The response of a WS is a json with first capitalize letters. I'm trying to encapsulate the response in a new MyResponse obj having lowercase first letters.
I'm using Jackson.
At first I have my models:
public class Telephone {
private String country;
private String prefix;
//getters and setters
}
public class Position {
private String x;
private String y;
//getters and setters
}
public class Root {
#JsonProperty("Telephone")
private List<Telephone> telephone;
#JsonProperty("Position")
private List<Position> position;
//getters and setters
}
public class MyResponse {
private final Root root;
private final String now;
public MyResponse(Root root, String now) {
this.root = root;
this.now = now;
}
//getters
}
As you can see above, I used #JsonProperty in my Root class because I want to map my response using a first lowercase letter.
Now I have my RestController:
#Controller
public class RestController {
#GetMapping("/my-controller")
ResponseEntity<String> myController() {
//Simulating the request to my ws to get my json string
String jsonString = "{\n" +
" \"Telephone\":[\n" +
" {\n" +
" \"country\":\"ES\",\n" +
" \"prefix\":\"+34\"\n" +
" },\n" +
" {\n" +
" \"country\":\"FR\",\n" +
" \"prefix\":\"+33\"\n" +
" },\n" +
" {\n" +
" \"country\":\"EN\",\n" +
" \"prefix\":\"+44\"\n" +
" }\n" +
" ],\n" +
" \"Position\":[\n" +
" {\n" +
" \"x\":\"123.23\",\n" +
" \"y\":\"98.93\"\n" +
" },\n" +
" {\n" +
" \"x\":\"250.99\",\n" +
" \"y\":\"43.89\"\n" +
" }\n" +
" ]\n" +
"}";
ObjectMapper om = new ObjectMapper();
Root root = null;
try {
root = om.readValue(jsonString, Root.class);
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
As you can see in the snippet of code above, at the beginning I got the json string (in my real code calling the WS) and I deserialized it into a Java POJO using the readValue method:
root = om.readValue(jsonString, Root.class);
Then I created my MyResponse obj using the deserialized POJO:
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
And at the end, I serialized myResponse obj to String using om.writeValueAsString and I returned it to my frontend:
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
Since MyResponse obj is serialized and deserialized both using my Root #JsonProperty (s)
I get:
{
"root":{
"Telephone":[
{
"country":"ES",
"prefix":"+34"
},
{
"country":"FR",
"prefix":"+33"
},
{
"country":"EN",
"prefix":"+44"
}
],
"Position":[
{
"x":"123.23",
"y":"98.93"
},
{
"x":"250.99",
"y":"43.89"
}
]
},
"now":"2021-06-24T11:18:04.077612"
}
That is not what I am trying to do: I have capitalize letters in my response.
How can I solve this problem? Should I use two different classes for serialization and deserialization?
You can specify your Jackson annotations on getters and setters as well to let them behave differently. Like this:
public class Root {
private List<Telephone> telephone;
#JsonProperty("Telephone")
private void setTelephone(String telephone) {
this.telephone = telephone;
}
#JsonProperty("telephone")
private String getTelephone() {
this.telephone = telephone;
}
}
I've tried what suggested by Felix. But it didn't work, I got the following error:
Conflicting/ambiguous property name definitions found multiple explicit names: but also implicit accessor
After a while I was able to solve my problem in this way:
public class Root {
private List<Telephone> telephone;
private List<Position> position;
#JsonCreator
public Root(#JsonProperty("Telephone") List<Telephone> telephon, #JsonProperty("Position") List<Position> position) {
this.telephon = telephon;
this.position = position;
}
//getters and setters
}
The annotation #JsonCreator is used in deserialization phase only.
When one deserializes Jackson uses the constructor annotated with #JsonCreator. In serialization phase Jackson uses the fields name to convert the obj into String.
Related
I have a JSON array string like below.
"[
{
"id" : "123",
"name":"John Doe",
"Address":"53/A"
},
{
"id" : "1234",
"name":"John Doe1",
"Address":"53/AB"
}
]"
I have a POJO class which maps with the inner JSON objects like below.
class User {
String id;
String name;
String Address;
//Getters & Setters
}
I want to create POJO objects from the above String and create a User ArrayList. How can I achieve this ? (using a library like Gson is fine). Thank you.
Gson, since you mentioned it, offers a method (link) which is exactly what you're looking for.
Gson.fromJson(java.lang.String json, java.lang.Class<T> classOfT)
If you want to deserialize a generic type (e.g. a list), docs advise you to use this method:
Gson.fromJson(java.lang.String json, java.lang.reflect.Type typeOfT)
There is also a tutorial (link) which tackles a few use-cases close to what you want to do.
ArrayList<LinkedTreeMap> list = gson.fromJson(json, ArrayList.class);
List<User> users= list.stream()
.map(s -> gson.fromJson(gson.toJson(s), User.class))
.collect(Collectors.toList());
You can use Jackson ObjectMapper and its very simple. below is the example code.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JsanParserExample {
public static void main(String[] args) throws IOException {
String json = "[\n" +
" {\n" +
" \"id\" : \"123\",\n" +
" \"name\":\"John Doe\",\n" +
" \"address\":\"53/A\"\n" +
" \n" +
" },\n" +
" {\n" +
" \"id\" : \"1234\",\n" +
" \"name\":\"John Doe1\",\n" +
" \"address\":\"53/AB\"\n" +
" \n" +
" }\n" +
" \n" +
"]";
ObjectMapper objectMapper = new ObjectMapper();
List<User> userList = objectMapper.readValue(json,new TypeReference<List<User>>() {});
userList.forEach(user -> System.out.println(user.getId()));
}
private static class User {
private String id;
private String name;
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
You can use Jackson. Simple example you can find here: https://www.baeldung.com/jackson-collection-array#to-array
Example:
ObjectMapper mapper = new ObjectMapper();
String jsonArray = "[{\"stringValue\":\"a\",\"intValue\":1,\"booleanValue\":true}, {\"stringValue\":\"bc\",\"intValue\":3,\"booleanValue\":false}]";
MyDto[] asArray = mapper.readValue(jsonArray, MyDto[].class);
This question already has an answer here:
Jackson JSON Deserialization with Root Element
(1 answer)
Closed 2 years ago.
I am trying to parse a JSON using Jackson,here is my class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Customer {
private String name;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
}
and the runner class
public class jsontoObj {
public static void main(String args[]) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = "{\n" +
" \"customer\":\n" +
" {\n" +
" \"name\": \"John Doeyy\"\n" +
" }\n" +
"}";
Customer customer = mapper.readValue( json, Customer.class);
System.out.println(customer.getName());
}
}
the setName method is working but getting null in customer.getName().
I don't want to use moxy
A correct json according to your mapping would be
String json = "{ \"name\": \"John Doeyy\" }";
I removed the embeded "customer" object
I am building out a client library for a somewhat complex API. However, there is significant commonality between different response objects, which look something like this:
{
"response": "Success",
"delay": 0.241,
"time": 125425234,
"message": null,
"a": "Payloads"
}
{
"response": "AuthFailure",
"delay": 0.112,
"time": 1324515123,
"message": "Wrong password",
"b": 1234
}
{
"response": "Success",
"delay": 0.294,
"time": 12461246123,
"message": null,
"c": True
"d": 245.1
}
I want to factor out the common parts, and hopefully deserialize them into a composition of objects:
Response<AData>
Response<BData>
Response<CDData>
(The class definitions would look something like):
class Response<T> {
final Response response;
final Double delay;
final Timestamp time;
final String message;
final T inner;
...
}
class AData {
final String a;
...
}
class BData {
final int b;
...
}
class BData {
final bool c;
final double d;
...
}
This is a lot like the inverse of the "JsonUnwrapped" Jackson annotation. Inheritence would work too.
Unfortunately, I cannot find a way to do this sensibly in Jackson in a way that would compose with the rest of the ObjectMapper system without writing a significant additional module. Am I missing something? Is there a better way to do this sort of thing?
The problem here is that you (or jackson) need to know what object need to be used to convert request. There is two ways of doing it:
1) use inheritance. This approach is more robust due to Jackson will handle everything for you but this approach requires to add a marker, that will be used by jackson to choose which object type should be used for conversion. Not sure that you can add these markers but the code below should give you an idea of how it can be done.
Its pretty simple -- you just need to add #JsonTypeInfo to configure which field will be used as a marker and #JsonSubTypes to define all the classes that can be used to convert response.
class ResponseA extends BaseResponse {
private String a;
}
class ResponseB extends BaseResponse {
private String b;
}
class ResponseCD extends BaseResponse {
private boolean c;
private double d;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseA.class, name = "a"),
#JsonSubTypes.Type(value = ResponseB.class, name = "b"),
#JsonSubTypes.Type(value = ResponseCD.class, name = "cd")
})
class BaseResponse {
private String response;
private double delay;
private long time;
private String message;
}
public class DynamicResponseInheritance {
private static final String RESPONSE_A = "{\n" +
" \"type\": \"a\",\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.241,\n" +
" \"time\": 125425234,\n" +
" \"message\": null,\n" +
" \"a\": \"Payloads\"\n" +
"}";
private static final String RESPONSE_B = "{\n" +
" \"type\": \"b\",\n" +
" \"response\": \"AuthFailure\",\n" +
" \"delay\": 0.112,\n" +
" \"time\": 1324515123,\n" +
" \"message\": \"Wrong password\",\n" +
" \"b\": 1234\n" +
"}";
private static final String RESPONSE_CD = "{\n" +
" \"type\": \"cd\",\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.294,\n" +
" \"time\": 12461246123,\n" +
" \"message\": null,\n" +
" \"c\": true,\n" +
" \"d\": 245.1\n" +
"}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);
System.out.println(responseA);
System.out.println(responseB);
System.out.println(responseCD);
}
}
2) implement custom deserializer. Its also pretty simple but in that case you will need to update deserializer if you need to add new class. The benefit of this approach is that you don't need to modify the response.
class ResponseDeserializer extends JsonDeserializer<BaseResponse> {
#Override
public BaseResponse deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode root = parser.getCodec().readTree(parser);
JsonNode a = root.get("a");
if (a != null) {
String content = root.toString();
return mapper.readValue(content, ResponseA.class);
}
JsonNode b = root.get("b");
if (b != null) {
String content = root.toString();
return mapper.readValue(content, ResponseB.class);
}
JsonNode c = root.get("c");
if (c != null) {
String content = root.toString();
return mapper.readValue(content, ResponseCD.class);
}
return null;
}
}
class ResponseA extends BaseResponse {
private String a;
}
class ResponseB extends BaseResponse {
private String b;
}
class ResponseCD extends BaseResponse {
private boolean c;
private double d;
}
class BaseResponse {
private String response;
private double delay;
private long time;
private String message;
}
public class DynamicResponseCustomDeserializer {
private static final String RESPONSE_A = "{\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.241,\n" +
" \"time\": 125425234,\n" +
" \"message\": null,\n" +
" \"a\": \"Payloads\"\n" +
"}";
private static final String RESPONSE_B = "{\n" +
" \"response\": \"AuthFailure\",\n" +
" \"delay\": 0.112,\n" +
" \"time\": 1324515123,\n" +
" \"message\": \"Wrong password\",\n" +
" \"b\": 1234\n" +
"}";
private static final String RESPONSE_CD = "{\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.294,\n" +
" \"time\": 12461246123,\n" +
" \"message\": null,\n" +
" \"c\": true,\n" +
" \"d\": 245.1\n" +
"}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(BaseResponse.class, new ResponseDeserializer());
objectMapper.registerModule(module);
BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);
System.out.println(responseA);
System.out.println(responseB);
System.out.println(responseCD);
}
}
This is not the inverse of #JsonUnwrapped. this IS #JsonUnwrapped, since the json specifies the variable inside the inner field of Response.
What you want is to tell Jackson what is the Generic construction of your data and you can achieve this with Jackson's internal type system:
This is the class definitions I used:
public class Response<T> {
public String response;
public Double delay;
public Timestamp time;
public String message;
#JsonUnwrapped
public T inner;
}
public class AData {
public String a;
}
public class BData {
public int b;
}
public class CData {
public boolean c;
public double d;
}
This is how you tell Jackson of generic construct:
ObjectMapper mapper = new ObjectMapper();
TypeFactory f = mapper.getTypeFactory();
JavaType responseAData = f.constructParametrizedType(Response.class, Response.class, AData.class);
Then deserialization can take place in the usual manner:
try (InputStream is = new FileInputStream("C://Temp/xx.json")) {
Response<AData> r = (Response<AData>) mapper.readValue(is, responseAData);
System.out.println(r.inner.a );
} catch (Exception e) {
e.printStackTrace();
}
Given input as specified in question, I got output as Payloads
The conly caveat here is that your client needs to know in advance the type of response
I have a json structure as shown below:
{
"clientId": 111,
"clientName": "abc",
"holder": [
{
"clientKey": "abc1",
"clientValue": {"hello" : "world"}
},
{
"clientKey": "abc2",
"recordValue": {}
}
]
}
I am deserializing my above JSON to my POJO using Jackson. Below is my POJO class where everything will get serialized.
import org.codehaus.jackson.annotate.JsonProperty;
public class DataRequest {
#JsonProperty("clientId")
private int clientId;
#JsonProperty("clientName")
private String clientName;
#JsonProperty("holder")
private List<ClientHolder> holder;
// getters and setters
public static class ClientHolder {
#JsonProperty("clientKey")
private String clientKey;
#JsonProperty("clientValue")
private Map<String, Object> clientValue;
// getters and setters
}
}
Is there any way I can have some sort of annotations in jackson that can do the validation while it is getting deserialized instead of doing the validations check after everything is deserialized? I want to validate below things:
clientId should be greater than zero.
clientName should never be null or empty string.
holder List should never be empty.
clientKey should never be null or empty string.
clientValue should never be null or empty as well.
Right now I am validating here:
private void validate(DataRequest request) {
if (request.getSchemaId() <= 0) {
// throw some exception
}
if (request.getClientName() == null || request.getClientName().isEmpty()) {
// throw some exception
}
// now I am not sure how should I do the validation for each
// clientKey and clientValue here efficiently
// if this is the only way we can do validations
}
You can consider using the Jackson Builder pattern support to build your model. Then you can put your validation code in the builder's build method. The build method will be called as a part of deserialisation. Here is a complete example with Jackson 2.X.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class JacksonBuilderValidation {
final static String JSON = "{\n"
+ " \"clientId\": 111,\n"
+ " \"clientName\": \"abc\",\n"
+ " \"recordValue\": [\n"
+ " {\n"
+ " \"clientKey\": \"abc1\",\n"
+ " \"clientValue\": {\"hello\" : \"world\"}\n"
+ " },\n"
+ " {\n"
+ " \"clientKey\": \"abc2\",\n"
+ " \"clientValue\": {}\n"
+ " }\n"
+ " ]}";
#JsonDeserialize(builder = JacksonBuilderValidation.DataRequest.Builder.class)
static class DataRequest {
private int clientId;
private String clientName;
private List<ClientHolder> recordValue;
private DataRequest(Builder builder) {
this.clientId = builder.clientId;
this.clientName = builder.clientName;
this.recordValue = builder.recordValue;
}
#Override
public String toString() {
return "DataRequest{" +
"clientId=" + clientId +
", clientName='" + clientName + '\'' +
", recordValue=" + recordValue +
'}';
}
static class ClientHolder {
public String clientKey;
public Map<String, Object> clientValue;
#Override
public String toString() {
return "ClientHolder{" +
"clientKey='" + clientKey + '\'' +
", clientValue=" + clientValue +
'}';
}
}
#JsonPOJOBuilder(withPrefix = "")
static class Builder {
private int clientId;
private String clientName;
private List<ClientHolder> recordValue;
Builder clientId(int clientId) {
this.clientId = clientId;
return this;
}
Builder clientName(String clientName) {
this.clientName = clientName;
return this;
}
Builder recordValue(List<ClientHolder> recordValue) {
this.recordValue = recordValue;
return this;
}
DataRequest build() {
final DataRequest dataRequest = new DataRequest(this);
// write validation code here
System.out.println("Is record value empty? "
+ dataRequest.recordValue.isEmpty());
return dataRequest;
}
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, DataRequest.class));
}
}
Output:
Is record value empty? false
DataRequest{clientId=111, clientName='abc', recordValue=[ClientHolder{clientKey='abc1', clientValue={hello=world}}, ClientHolder{clientKey='abc2', clientValue={}}]}
Google's cAdvisor API gives JSON output like this:
{
/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope: {},
/system.slice/docker-747f797d19931b4ef33cda0c519f935b592a0b828d16b8cafc350568ab2c1d28.scope: {},
/system.slice/docker-bf947bfabf61cd5168bd599162cf5f5c2ea2350eece1ded018faebf598f7ee5b.scope: {},
/system.slice/docker-e8e02d508400438603151dd462ef036d59fada8239f66be8e64813880b59a77d.scope: {
name: "/system.slice/docker-e8e02d508400438603151dd462ef036d59fada8239f66be8e64813880b59a77d.scope",
aliases: [...],
namespace: "docker",
spec: {...},
stats: [...]
}
}
I would describe this as 4 same-typed JSON objects with variable/anonymous names, held in an anonymous object.
My first thought would just do something like mapper.readValue(response, Containers.class), where:
public class Containers extends BaseJsonObject {
#JsonProperty
public List<Container> containerList;
}
and
public class Container extends BaseJsonObject {
#JsonProperty
private String name;
#JsonProperty
public String[] aliases;
#JsonProperty
private String namespace;
#JsonProperty
private String spec;
#JsonProperty
public Stats[] stats;
}
But all of the variations on this I can think of yield the same result: some permutation of com.xyz.Containers#45c7e403[containerList=<null>] or com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope" (class com.xyz.Containers), not marked as ignorable (one known property: "containerList"])
at [Source: java.io.StringReader#3d285d7e; line: 1, column: 97] (through reference chain: com.xyz.Containers["/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope"]), with ACCEPT_SINGLE_VALUE_AS_ARRAY = false .
I've tried:
mapper.readValue(response, Container[].class)
mapper.readValue(response, Containers.class)
mapper.readValues(jsonParser, Container.class)
As well as the following configurations:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
How can I parse JSON objects with variable/anonymous names, held in a non-array? What is this called?
You can use the #JsonAnySetter annotation as follows and put the objects with variable names into a map of Map<String, Container>.
Here is an example:
public class JacksonVariableNames {
static final String JSON = "{\n"
+ " \"a\": {\n"
+ " \"value\": \"1\"\n"
+ " },\n"
+ " \"b\": {\n"
+ " \"value\": \"2\"\n"
+ " },\n"
+ " \"c\": {\n"
+ " \"value\": \"3\"\n"
+ " }\n"
+ "}";
static class Value {
private final String value;
#JsonCreator
Value(#JsonProperty("value") final String value) {this.value = value;}
#Override
public String toString() {
return "Value{" +
"value='" + value + '\'' +
'}';
}
}
static class Values {
private final Map<String, Value> values = new HashMap<>();
#JsonAnySetter
public void setValue(final String property, final Value value) {
values.put(property, value);
}
#Override
public String toString() {
return "Values{" +
"values=" + values +
'}';
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Values.class));
}
}
Output:
Values{values={a=Value{value='1'}, b=Value{value='2'}, c=Value{value='3'}}}