t3ng1l
t3ng1l

Reputation: 1065

Trouble serializing with to JSON in Java using Jackson

Good morning guys!

I have a JSON strings that looks like:

{
   "StatusCode":0,
   "Message":null,
   "ExecutionTime":0,
   "ResponseData":[
        {"Name":"name1","SiteId":"1234","Type":"Type1","X":"1234567","Y":"123456"},
        {"Name":"Name2","SiteId":"2134","Type":"Type2","X":"1234567","Y":"1234567"},
        {"Name":"Name3","SiteId":"3241","Type":"Type3","X":"1234567","Y":"1234567"},
        {"Name":"Name4","SiteId":"4123","Type":"Type4","X":"123456","Y":"123456"}
    ]
}

I want to create an object where I can retrieve the Xand Y values.

I've been trying to use Jackson to serialize the JSON string, without success. I've created two extra classes for Jackson to use. One class for the top layer, StatusCode, Message, ExecutionTime and ResponseData which looks like

public class PL {
private Long statusCode;
private String executionTime;
private String message;
private ResponseData responseData;
public PL(){
}

public void setStatusCode(Long statusCode){
  this.statusCode = statusCode;
}

public Long getStatusCode(){
  return this.statusCode;
}

public void setExecutionTime(String executionTime){
  this.executionTime = executionTime;
}
public String getExecutionTime(){
  return this.executionTime;
}

public void setMessage(String message){
  this.message = message;
}
public String getMessage(){
  return this.message;
}

public void setResponseData(ResponseData responseData){
  this.responseData = responseData;
}
public ResponseData getResponseData(){
  return this.responseData;
}
}

Where ReponseData is returned as an object, and then I have another class for serializing ResponseData which looks like

public class ResponseData {

private String name;
private String siteId;
private String type;
private String x;
private String y;

public ResponseData(){
}

public void setName(String name){
  this.name = name;
}
public String getName(){
  return this.name;
}

public void setSiteId(String siteId){
  this.siteId = siteId;
}
public String getSiteId(){
  return this.siteId;
}

public void setType(String type){
  this.type = type;
}
public String setType(){
  return this.type;
}

public void setX(String x){
  this.x = x;
}
public String getX(){
  return this.x;
}

public void setY(String y){
  this.y = y;
}
public String getY(){
  return this.y;
}
}

I then create an ObjectMapper with

private final static ObjectMapper mapper = new ObjectMapper();

and try to so read the values with

ResponseData e = mapper.readValue(result.toString(), ResponseData.class);

and end up with the exception

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "StatusCode" (class MyClass.ResponseData), not marked as ignorable (5 known properties: "x", "y", "siteId", "name", "type"])

as if it can't parse the first entry, StatusMessage. Even if I remove the second class and only try to parse the first four entries where i return ResponseData as a String I still get the same exception.

Upvotes: 3

Views: 771

Answers (3)

Bentaye
Bentaye

Reputation: 9766

To start with, in PL you should have a List<ResponseData> not a simple ResponseData attribute. As you can see, in the JSON, ResponseData is an array "ResponseData":[...] so it will be deserialized as a List. Each element of the list will be a ResponseData object as you defined it.

Then you have a case issue, you have upper cases in the JSON that you don't have in your class attributes. You can use the @JsonProperty (See API) annotation to overcome the problem, this way:

class PL {
    @JsonProperty("StatusCode")
    private Long statusCode;
    @JsonProperty("ExecutionTime")
    private String executionTime;
    @JsonProperty("Message")
    private String message;
    @JsonProperty("ResponseData")
    private List<ResponseData> responseDatas;

    public PL(){
    }

    // getters/Setters  

}


class ResponseData {

    @JsonProperty("Name")
    private String name;
    @JsonProperty("SiteId")
    private String siteId;
    @JsonProperty("Type")
    private String type;
    @JsonProperty("X")
    private String x;
    @JsonProperty("Y")
    private String y;

    public ResponseData(){
    }

    // getters/Setters  

}

Then read your JSON as a PL object, like this:

ObjectMapper mapper = new ObjectMapper();
PL pl = mapper.readValue(json, PL.class);
for(ResponseData rd : pl.getResponseDatas()) {
    System.out.println(rd.getX());
    System.out.println(rd.getY());
}

This outputs:

1234567
123456
1234567
1234567
1234567
1234567
123456
123456

Upvotes: 3

Atul Sharma
Atul Sharma

Reputation: 10740

Use List to receive arrays.

private Long statusCode;
private String executionTime;
private String message;
public List<ResponseDataType> ResponseData

and it will do everything automatically.

Upvotes: 1

Oleg Sklyar
Oleg Sklyar

Reputation: 10092

It is fairly straightforward. Define your response structure using composition of classes. It is unfortunate to use capitalised fields in JSON, which out-of-the-box requires capitalised field names in the Java DTO. Still those can be easily mapped to conventional low-case names either by using the ACCEPT_CASE_INSENSITIVE_PROPERTIES modifier on the ObjectMapper or by annotating fields with corresponding names. I prefer a property on the ObjectMapper as it keeps the DTO independent of the serialisation code and this technique is used in the test below (the test is green):

import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestDeserialization50386188 {

    public static class Response {

        public static class ResponseDataType {
            public String name;
            public String siteId;
            public String type;
            public long x;
            public long y;
        }

        public int statusCode;
        public String message;
        public long executionTime;
        public List<ResponseDataType> ResponseData = new ArrayList<>();
    }

    private static final String data = "{\"StatusCode\":0,\"Message\":null,\"ExecutionTime\":0,\"ResponseData\":[{\"Name\":\"name1\",\"SiteId\":\"1234\",\"Type\":\"Type1\",\"X\":\"1234567\",\"Y\":\"123456\"},{\"Name\":\"Name2\",\"SiteId\":\"2134\",\"Type\":\"Type2\",\"X\":\"1234567\",\"Y\":\"1234567\"},{\"Name\":\"Name3\",\"SiteId\":\"3241\",\"Type\":\"Type3\",\"X\":\"1234567\",\"Y\":\"1234567\"},{\"Name\":\"Name4\",\"SiteId\":\"4123\",\"Type\":\"Type4\",\"X\":\"123456\",\"Y\":\"123456\"}]}";

    @Test
    public void deserialize_response_withJackson_ok() throws IOException {
        ObjectMapper mapper = new ObjectMapper()
          .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

        Response response = mapper.readValue(data, Response.class);

        assertEquals(4, response.ResponseData.size());
        assertEquals(1234567, response.ResponseData.get(2).x);
        assertEquals(1234567, response.ResponseData.get(2).y);
    }
}

You fill find the project with the executable test on this dedicated GitHub repo.

The "Clean Code" book by Uncle Bob does not really recommend the overuse of getters and setters so common in Java for DTOs, which a Response class is. Still you can replace all public fields with getter/setter pairs if you like but the clarity will suffer with no obvious gain on quality.

Upvotes: 2

Related Questions