We are using Spring Boot to expose a REST endpoint which is called by a dumb client which delivers us the following:
{
"timestamp": "2016-08-16T14:30.000Z",
"data": "{\"amount\":1,\"product\":\"BASIC PRODUCT\"}"
}
We've created the following objects:
#JsonDeserialize(builder = Message.Builder.class)
public final class Message {
private final String timestamp;
private final Data data;
public String getTimestamp() {...}
public Data getData() {...}
#JsonPOJOBuilder
public static final class Builder {
private String timestamp;
private Data data;
public Builder withTimestamp(final String timestamp) {...}
public Builder withData(final Data data) {...}
}
}
and
#JsonDeserialize(builder = Data.Builder.class)
public final class Data {
private final String product;
private final int amount;
public String getProduct() {...}
public int getAmount() {...}
#JsonPOJOBuilder
public static final class Builder {
private String product;
private int amount;
public Builder withProduct(final String product) {...}
public Builder withAmount(final int amount) {...}
}
}
and exposed the endpoint as
#RequestMapping(consumes = "application/json", method = POST)
public ResponseEntity<?> receive(#RequestBody Message message) {
/// ...
}
but control doesn't even reach the receive method and fails with 400 BAD REQUEST. I believe this has to do with the fact that data is a JSON-valued string. Does Jackson provide any annotation that I can use to force the JSON-valued string to be deserialized as an instance of Data?
The key is in public Builder withData() method of Message.Builder.class to explicitly parse JSON-valued string to Data type. Change the method parameter to String instead of Data and call ObjectMapper().readValue(JSON-valued string, Data.class) to deserialize it into Data.
For example like this:
public Builder withData(final String jsonValue) throws JsonParseException, JsonMappingException, IOException {
Data data = new ObjectMapper().readValue(jsonValue, Data.class);
this.data = data;
return this;
}
For the clarity sake here you are my whole POJOs:
Message:
public final class Message {
private final String timestamp;
private final Data data;
private Message(Builder builder){
this.timestamp = builder.timestamp;
this.data = builder.data;
}
public String getTimestamp() {...}
public Data getData() {...}
#JsonPOJOBuilder
public static final class Builder {
private String timestamp;
private Data data;
private static ObjectMapper mapper = new ObjectMapper();
public Builder withTimestamp(final String timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder withData(final String jsonValue) throws JsonParseException, JsonMappingException, IOException {
Data data = mapper.readValue(jsonValue, Data.class);
this.data = data;
return this;
}
public Message build() {
return new Message(this);
}
} // Builder
}
Data:
public final class Data {
private final String product;
private final int amount;
private Data(Builder builder){
this.product = builder.product;
this.amount = builder.amount;
}
public String getProduct() {...}
public int getAmount() {...}
#JsonPOJOBuilder
public static final class Builder {
private String product;
private int amount;
public Builder withProduct(final String product) {
this.product = product;
return this;
}
public Builder withAmount(final int amount) {
this.amount = amount;
return this;
}
public Data build() {
return new Data(this);
}
} // Builder
}
Hope it helps.
Related
I am trying to serialise an object by converting it first to another object. At the same time I do not want to serialise twice the same object so I am using #JsonIdentityReference to serialise it only the first time. However since the converter will always create a new output object for the same input object the entire object is serialised every time. Is there a way to avoid serialising twice the same object while at the same time using a converter?
The Domain class
#JsonSerialize(converter = BooleanWithNameConverter.class)
#JsonDeserialize(converter = BooleanWithNameDtoConverter.class)
public class BooleanWithName {
private final boolean value;
private final String name;
BooleanWithName(String name, boolean value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public boolean getValue() {
return value;
}
}
The DTO
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
public class BooleanWithNameDto {
private final boolean value;
private final String name;
#JsonCreator
BooleanWithNameDto(#JsonProperty("name") String name, #JsonProperty("value") boolean value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public boolean getValue() {
return value;
}
}
The converter to the DTO
public class BooleanWithNameConverter extends StdConverter<BooleanWithName, BooleanWithNameDto> {
#Override
public BooleanWithNameDto convert(BooleanWithName obj) {
return new BooleanWithNameDto(obj.getName(), obj.getValue());
}
}
The converter back to the domain object
This is not strictly needed since the problem can be seen just by examining the json string but helpful in order to add a small unit test demonstrating the problem
public class BooleanWithNameDtoConverter extends StdConverter<BooleanWithNameDto, BooleanWithName> {
#Override
public BooleanWithName convert(BooleanWithNameDto obj) {
return new BooleanWithName(obj.getName(), obj.getValue());
}
}
A small unit test that fails
public class UnitTest {
#Test
public void testConverter() throws JsonProcessingException {
BooleanWithName booleanWithName = new BooleanWithName("dummy", true);
ObjectMapper objectMapper = new ObjectMapper();
BooleanWithName[] value = { booleanWithName, booleanWithName };
BooleanWithName[] readValue = objectMapper.readValue(objectMapper.writeValueAsString(value), BooleanWithName[].class);
Assertions.assertEquals(readValue[0], readValue[1]);
}
}
Thanks a lot!
I try to serialize and deserialize an object DataSourceObject that wraps a Serializable object, but I don't know the type of the wrapped object.
When I deserialize the JSON, I get an exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.accor.assets.TestSerialization$DataSourceObject` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"#class":"com.accor.assets.TestSerialization$DataSourceObject","concreteObject":{"#class":"com.accor.assets.TestSerialization$HotelsListBean","version":"1.0","metaResponse":{"#class":"com.accor.assets.TestSerialization$MetaResponse","returncode":"0","date":"10/28/21 09:39:14 AM"},"hotelsList":{"#class":"com.accor.assets.TestSerialization$HotelsList","hotel":["java.util.ArrayList",[{"#class":"com.accor.assets.TestSerialization$Hotel","name":"My Hotel","code":"H001","nbstars":"4","countryCode":"G"[truncated 8 chars]; line: 1, column: 65]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1415)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
Here is a complete example to reproduce the problem:
public class TestSerialization {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = objectMapper();
HotelsListBean hotelsListBean = new HotelsListBean();
hotelsListBean.setVersion("1.0");
MetaResponse metaResponse = new MetaResponse();
metaResponse.setReturncode("0");
metaResponse.setDate("10/28/21 09:39:14 AM");
hotelsListBean.setMetaResponse(metaResponse);
HotelsList hotelsList = new HotelsList();
Hotel hotel = new Hotel();
hotel.setCode("H001");
hotel.setName("My Hotel");
hotel.setCountryCode("GB");
hotel.setNbstars("4");
hotelsList.getHotel().add(hotel);
hotelsListBean.setHotelsList(hotelsList);
DataSourceObject<HotelsListBean> dataSourceObject = new DataSourceObject<>(hotelsListBean);
String json = objectMapper.writeValueAsString(dataSourceObject);
System.out.println(json);
Object result = objectMapper.readValue(json, Object.class);
System.out.println(result);
}
private static ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// Register support of other new Java 8 datatypes outside of date/time: most notably Optional, OptionalLong, OptionalDouble
objectMapper.registerModule(new Jdk8Module());
// Register support for Java 8 date/time types (specified in JSR-310 specification)
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
public static class DataSourceObject<T extends Serializable> implements Serializable {
private static final long serialVersionUID = -6026669040755678830L;
private final T concreteObject;
public DataSourceObject(final T concreteObject) {
this.concreteObject = concreteObject;
}
public T getConcreteObject() {
return concreteObject;
}
}
public static class HotelsListBean implements Serializable {
private final static long serialVersionUID = 1L;
protected String version;
protected MetaResponse metaResponse;
protected HotelsList hotelsList;
public String getVersion() {
return version;
}
public void setVersion(String value) {
this.version = value;
}
public MetaResponse getMetaResponse() {
return metaResponse;
}
public void setMetaResponse(MetaResponse value) {
this.metaResponse = value;
}
public HotelsList getHotelsList() {
return hotelsList;
}
public void setHotelsList(HotelsList hotelsList) {
this.hotelsList = hotelsList;
}
}
public static class MetaResponse implements Serializable {
private final static long serialVersionUID = 1L;
protected String returncode;
protected String date;
public String getReturncode() {
return returncode;
}
public void setReturncode(String value) {
this.returncode = value;
}
public String getDate() {
return date;
}
public void setDate(String value) {
this.date = value;
}
}
public static class HotelsList implements Serializable {
private final static long serialVersionUID = 1L;
protected List<Hotel> hotel;
public List<Hotel> getHotel() {
if (hotel == null) {
hotel = new ArrayList<Hotel>();
}
return this.hotel;
}
}
public static class Hotel implements Serializable {
private final static long serialVersionUID = 1L;
protected String name;
protected String code;
protected String nbstars;
protected String countryCode;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getCode() {
return code;
}
public void setCode(String value) {
this.code = value;
}
public String getNbstars() {
return nbstars;
}
public void setNbstars(String value) {
this.nbstars = value;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String value) {
this.countryCode = value;
}
}
}
I would like to know what is possible to successfully deserialize.
(I tried to add #JsonCreator on the DataSourceObject constructor but I get the same exception)
You need to have a no-args constructor for DataSourceObject. You have two options.
Make concreteObject not final:
public static class DataSourceObject<T extends Serializable> implements Serializable {
private static final long serialVersionUID = -6026669040755678830L;
private T concreteObject;
private DataSourceObject() { }
public DataSourceObject(final T concreteObject) {
this.concreteObject = concreteObject;
}
public T getConcreteObject() {
return concreteObject;
}
}
Keep concreteObject final, but it must be assigned to null in the no-args constructor:
public static class DataSourceObject<T extends Serializable> implements Serializable {
private static final long serialVersionUID = -6026669040755678830L;
private final T concreteObject;
private DataSourceObject() {
concreteObject = null;
}
public DataSourceObject(final T concreteObject) {
this.concreteObject = concreteObject;
}
public T getConcreteObject() {
return concreteObject;
}
}
Either option will work.
I have a nested java map like this
inputMap: {jobId={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502260561923, root/im]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returncode=4096, messageId=null, arguments=null, message=null}
which I want to map to java pojo and here is my pojo classes.
#Getter
#Setter
#ToString
public class DMResponseMapper {
#Getter
#Setter
#ToString
public static class GetSysConfigDMResponseMapper {
#JsonProperty("jobId")
private EndpointReferenceMapper endpointReferenceMapper;
private Integer returnCode;
private String messageId;
private String arguments;
private String message;
#Getter
#Setter
#ToString
public static class EndpointReferenceMapper {
#JsonProperty("ReferenceParameters")
private ReferenceParametersMapper referenceParametersMapper;
#JsonProperty("Address")
private String address;
#Getter
#Setter
#ToString
public static class ReferenceParametersMapper {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSetMapper selectorSetMapper;
#Getter
#Setter
#ToString
public static class SelectorSetMapper {
#JsonProperty("Selector")
private List<String> selector;
}
}
}
}
}
but objectMapper.convertValue(inputMap, GetSysConfigDMResponseMapper.class) is NOT mapping the nested classes.. just the top level fields.
My objectMapper is instantiated like this:
static {
objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Response Object is :
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper=DMResponseMapper.GetSysConfigDMResponseMapper.EndpointReferenceMapper(referenceParametersMapper=null, address=null), returnCode=4096, messageId=null, arguments=null, message=null)
Can anyone please suggest, what is wrong here?
Upon debugging this is what I see:
Converted endpointReferenceMapper to type Object.
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502318722705, root/dcim]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returnCode=4096, messageId=null, arguments=null, message=null)
The DMResponseMapper pojo needs to follow the structure of your source data more closely.
Your source Map object has the following structure, based on the info in the question:
inputMap:
{
jobId={
EndpointReference={
ReferenceParameters={
ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job,
SelectorSet={
Selector=[JID_502260561923, root/im]
}
},
Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
}
},
returncode=4096,
messageId=null,
arguments=null,
message=null
}
So, I adapted your DMResponseMapper pojo class to more closely map to that structure - and I changed the nested class names as well. Here is a summary of the nested classes with their fields for your data:
//
// NOT the actual class - just an overview of the structure!
//
class DMResponseMapper {
private JobId jobId;
private Integer returncode;
private Object messageId;
private Object arguments;
private Object message;
class JobId {
private EndpointReference endpointReference;
class EndpointReference {
private ReferenceParameters referenceParameters;
private String address;
class ReferenceParameters {
private String resourceURI;
private SelectorSet selectorSet;
class SelectorSet {
private List<String> selector = null;
}
}
}
}
}
This gave me the following, when fleshed out with annotations and getters/setters:
//
// Here is the actual class, based on the above structure.
//
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class DMResponseMapper {
#JsonProperty("jobId")
private JobId jobId;
#JsonProperty("returncode")
private Integer returncode;
#JsonProperty("messageId")
private Object messageId;
#JsonProperty("arguments")
private Object arguments;
#JsonProperty("message")
private Object message;
#JsonProperty("jobId")
public JobId getJobId() {
return jobId;
}
#JsonProperty("jobId")
public void setJobId(JobId jobId) {
this.jobId = jobId;
}
#JsonProperty("returncode")
public Integer getReturncode() {
return returncode;
}
#JsonProperty("returncode")
public void setReturncode(Integer returncode) {
this.returncode = returncode;
}
#JsonProperty("messageId")
public Object getMessageId() {
return messageId;
}
#JsonProperty("messageId")
public void setMessageId(Object messageId) {
this.messageId = messageId;
}
#JsonProperty("arguments")
public Object getArguments() {
return arguments;
}
#JsonProperty("arguments")
public void setArguments(Object arguments) {
this.arguments = arguments;
}
#JsonProperty("message")
public Object getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(Object message) {
this.message = message;
}
public static class JobId {
#JsonProperty("EndpointReference")
private EndpointReference endpointReference;
#JsonProperty("EndpointReference")
public EndpointReference getEndpointReference() {
return endpointReference;
}
#JsonProperty("EndpointReference")
public void setEndpointReference(EndpointReference endpointReference) {
this.endpointReference = endpointReference;
}
public static class EndpointReference {
#JsonProperty("ReferenceParameters")
private ReferenceParameters referenceParameters;
#JsonProperty("Address")
private String address;
#JsonProperty("ReferenceParameters")
public ReferenceParameters getReferenceParameters() {
return referenceParameters;
}
#JsonProperty("ReferenceParameters")
public void setReferenceParameters(ReferenceParameters referenceParameters) {
this.referenceParameters = referenceParameters;
}
#JsonProperty("Address")
public String getAddress() {
return address;
}
#JsonProperty("Address")
public void setAddress(String address) {
this.address = address;
}
public static class ReferenceParameters {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSet selectorSet;
#JsonProperty("ResourceURI")
public String getResourceURI() {
return resourceURI;
}
#JsonProperty("ResourceURI")
public void setResourceURI(String resourceURI) {
this.resourceURI = resourceURI;
}
#JsonProperty("SelectorSet")
public SelectorSet getSelectorSet() {
return selectorSet;
}
#JsonProperty("SelectorSet")
public void setSelectorSet(SelectorSet selectorSet) {
this.selectorSet = selectorSet;
}
public static class SelectorSet {
#JsonProperty("Selector")
private List<String> selector = null;
#JsonProperty("Selector")
public List<String> getSelector() {
return selector;
}
#JsonProperty("Selector")
public void setSelector(List<String> selector) {
this.selector = selector;
}
}
}
}
}
}
This is invoked as follows:
First, some test data:
List<String> selector = new ArrayList();
selector.add("JID_502260561923");
selector.add("root/im");
Map<String, Object> selectorSet = new HashMap();
selectorSet.put("Selector", selector);
String resourceURI = "http://schemas.com/wbem/wscim/1/cim-schema/2/Job";
Map<String, Object> referenceParameters = new HashMap();
referenceParameters.put("ResourceURI", resourceURI);
referenceParameters.put("SelectorSet", selectorSet);
String address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
Map<String, Object> endpointReference = new HashMap();
endpointReference.put("ReferenceParameters", referenceParameters);
endpointReference.put("Address", address);
Map<String, Object> jobId = new HashMap();
jobId.put("EndpointReference", endpointReference);
Map<String, Object> inputMap = new HashMap();
inputMap.put("jobId", jobId);
inputMap.put("returncode", 4096);
inputMap.put("messageId", "foo");
inputMap.put("arguments", "bar");
inputMap.put("message", "baz");
Note I replaced your null values with strings, for testing and demonstration.
Then the code to perform the mapping:
ObjectMapper objectMapper = new ObjectMapper();
DMResponseMapper mapper = objectMapper.convertValue(inputMap, DMResponseMapper.class);
The resulting mapper object contains the test data:
I have an API which returns the following schema for all requests to collections of models:
{
item_count: 83,
items_per_page: 25,
offset: 25,
<Model>s: [
{ ... },
{ ... },
{ ... },
...
]
}
For instance, if I make a request to /api/v1/customers then this JSON will contain a customers key. If I make a request to /api/v1/products then this JSON will contain a products key.
I with to create a generic PaginatedResponse<T> class to handle the item_count, items_per_page, and offset variables like so:
public class PaginatedResponse<T> {
private int item_count;
private int items_per_page;
private int offset;
private List<T> data;
public PaginatedResponse<T>(int item_count, int items_per_page, int offset, List<T> data) {
this.item_count = item_count;
this.items_per_page = items_per_page;
this.offset = offset;
this.data = data;
}
public List<T> getData() {
return this.data;
}
}
Is there a way to parse this JSON into my PaginatedResponse POJO?
As you have different keys for model list, <Model>s:, IMHO, you better use different models for each response. You have to remove private List<T> data; from the base response model and move it to the child model.
I've modified your code and created some sample models for your products and customers. Below given detailed example,
BasePaginatedResponse.java
public class BasePaginatedResponse {
private int item_count;
private int items_per_page;
private int offset;
public BasePaginatedResponse(
int item_count, int items_per_page, int offset) {
this.item_count = item_count;
this.items_per_page = items_per_page;
this.offset = offset;
}
}
CustomersResponse.java
public class CustomersResponse extends BasePaginatedResponse {
private final List<Customer> customers;
public CustomersResponse(int item_count, int items_per_page, int offset, List<Customer> customers) {
super(item_count, items_per_page, offset);
this.customers = customers;
}
public List<Customer> getCustomers() {
return customers;
}
public class Customer {
private final String id, name;
public Customer(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
}
ProductsResponse.java
public class ProductsResponse extends BasePaginatedResponse {
private final List<Customer> products;
public ProductsResponse(int item_count, int items_per_page, int offset, List<Customer> products) {
super(item_count, items_per_page, offset);
this.products = products;
}
public List<Customer> getProducts() {
return products;
}
public class Customer {
private final String id, name;
public Customer(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
}
Here, I've created 3 classes. 1 base response class (parent), and 2 child classes.
Parent class contains fields that are common to both child classes.
As you're using Retrofit, your ApiInterface should be something like this
interface ApiInterface{
#GET("api/v1/customers")
Call<CustomersResponse> getCustomers();
#GET("api/v1/products")
Call<ProductsResponse> getProducts();
}
If you need more clarification, ask me in the comments.
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.
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
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.
Use List to receive arrays.
private Long statusCode;
private String executionTime;
private String message;
public List<ResponseDataType> ResponseData
and it will do everything automatically.