Reputation: 1065
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 X
and 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
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
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
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