Java: Deserialize object with list of Objects - java

I have this JSON: (passed as Map<String, Object>)
{
"id": 1000,
"lab": [
"LAB1",
"LAB2",
"LAB3"
],
"name": "TEST",
"ref": {
"id": 1000,
"code": "REFCODE",
"description": "REF DESC"
},
"employee": {
"id": 1000,
"name": "Emp1000",
"tin": null,
"active": true
},
"contacts": [
{
"id": 1000,
"name": "Contact 1",
"emailAddress": "contact1#test.com",
"active": true,
"positions": [
{
"position": {
"id": 1000,
"code": "POS",
"description": "POS DESC"
}
}
]
}
],
"status": "NEW"
}
This is my DTO and ContactDTO:
public class DTO {
private Long id;
...
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
public class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
#NotEmpty
private List<ContactPositionDTO> positions;
}
Here is my service class with object mapper and process method which accepts the JSON map:
private ObjectMapper objectMapper() {
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return objectMapper;
}
public void process(final Map<String, Object> map) {
objectMapper().convertValue(map, DTO.class);
}
However, I am getting java.lang.IllegalArgumentException: Cannot deserialize value of type java.util.ArrayList
And if I add DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY I am getting a different error:
java.lang.IllegalArgumentException: Cannot construct instance of ContactDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('[{id=1000, name=Contact 1, .....

You have two alternative options to fix your ContactDTO class:
Add a no-arguments constructor
public ContactDTO() { }
to the class. To fix the then upcoming compiler error
you will need to remove the #Builder annotation.
Keep the #Builder annotation
and add the #Jacksonized annotation to the class.
This will configure the generated builder to cooperate
with Jackson's deserialization.
For more details see Lombok's documentation about #Jacksonized.

To deserialize you need a No arg constructor and to use #Builder you need an all arg constructor.
So you need tu add both.
The example below should work. I just added #Getter annotation to avoid using #JsonProperty
#Getter
public static class DTO {
private Long id;
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public static class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
private List<ContactPositionDTO> positions;
}
#Getter
public static class ContactPositionDTO {
private Position position;
#JsonProperty("effectiveStartDate")
private List<Integer> date;
#Getter
static class Position {
private Integer id;
private String code;
private String description;
}
}
NB: you can also use #Jacksonized annotation instead of #NoArgsConstructor and #AllArgsConstructor

Related

Can an API response root property be removed from a Java POJO response?

I have been wondering what exactly I am doing wrong here. The response I am getting from my POJO class has a root property that I am unable to remove.
I have this JSON response:
{
"page": 1,
"per_page": 6,
"total": 12,
"total_pages": 2,
"data": [
{
"id": 1,
"name": "cerulean",
"year": 2000,
"color": "#98B2D1",
"pantone_value": "15-4020"
},
{
"id": 2,
"name": "fuchsia rose",
"year": 2001,
"color": "#C74375",
"pantone_value": "17-2031"
}
],
"support": {
"url": "https://reqres.in/#support-heading",
"text": "To keep ReqRes free, contributions towards server costs are appreciated!"
}
}
I converted JSON to these POJO classes and ignore properties not required for my test.
First POJO
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Datum{
public int id;
public String name;
public int year;
public String color;
public String pantone_value;
}
Second POJO
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Root {
#JsonIgnore
public int page;
#JsonIgnore
public int per_page;
#JsonIgnore
public int total;
#JsonIgnore
public int total_pages;
public ArrayList<Datum> data;
#JsonIgnore
public Support support;
}
Third POJO:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Support {
public String url;
public String text;
}
I want to get the properties in the Responses' Data list and convert it to a map, so I did this:
public void verify( List<Map<String, String>> myTest) { //myTest holds the expected response i want to use for my assertion
Root response = (resp.as(Root.class));
Map<String, Object> mapResponse = mapper.convertValue(response, new TypeReference<>() {
});
System.out.println(mapResponse);
}
Output:
{data=[{id=1, name=cerulean, year=2000, color=#98B2D1, pantone_value=15-4020}, {id=2, name=fuchsia rose, year=2001, color=#C74375, pantone_value=17-2031}, {id=3, name=true red, year=2002, color=#BF1932, pantone_value=19-1664}]}
The {data= root property (key) at beginning of the output is what I was trying to remove as it's making my assertion fail.
This is the output I would like:
[{id=1, name=cerulean, year=2000, color=#98B2D1, pantone_value=15-4020}, {id=2, name=fuchsia rose, year=2001, color=#C74375, pantone_value=17-2031}]
How can I convert the response's data format to get this?
You can convert only data list
List<Map<String, Object>> mapResponse = mapper.convertValue(response.getData(), new TypeReference<>() {
});
System.out.println(mapResponse);

How to map attribute value into two fields during Jackson deserialisation

I would like to know is there a way (probably using deserialiser) in Jackson to copy one attribute value to another object attribute the container has.
For example, documentId from the TestData class also needs to be persisted in the Details.documentId attribute.
json/TestData.json
{
"id": "1",
"documentId" : "234234",
"details" : {
"name": "test",
"lastName": "asdf"
}
}
#RequiredArgsConstructor(onConstructor_ = #Autowired)
#SpringBootTest(classes = ExampleMicroserviceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
#ActiveProfiles("test")
public class TestDocumentId {
final ObjectMapper objectMapper;
#Value("classpath:json/TestData.json")
Resource testData;
#SneakyThrows
#Test
void testDocumentIdPresentInDetails() {
var data = objectMapper.readValue(testData.getFile(), TestData.class);
assertThat(data.documentId).isNotNull();
assertThat(data.getDetails().name).isNotNull();
assertThat(data.getDetails().documentId).isNotNull();
}
#Data
public static class TestData {
private String id;
private String documentId;
private Details details;
#Data
public static class Details {
private String documentId;
private String name;
private String lastName;
}
}
}

Jackson-databind mapping JSON skip layer

I got a JSON response like this:
{
"status": "success",
"response": {
"entries": [
{
"id": 1,
"value": "test"
},
{
"id": 2,
"value": "test2"
}
]
}
}
And i want to map it with jackson-databind on an object like this:
public class Response {
#JsonProperty("status")
private String status;
#JsonProperty("response.entries")
private Collection<ResponseEntry> entries;
}
So i'm searching for an way to give #JsonProperty a path so it can skip the layer "response".
Welcome to Stack Overflow. You can define a wrapper class for your Collection<ResponseEntry> collection like below :
public class ResponseWrapper {
#JsonProperty("entries")
private Collection<ResponseEntry> entries;
}
The ResponseEntry class could be defined like below :
public class ResponseEntry {
#JsonProperty("id")
private int id;
#JsonProperty("value")
private String value;
}
Once defined these classes you can rewrite your old Response class like below :
public class Response {
#JsonProperty("status")
private String status;
#JsonProperty("response")
private ResponseWrapper responseWrapper;
}
You can flatten using the #JsonUnwrapped annotation.
You can have your classes like this
public class Response {
private String status;
private Collection<ResponseEntry> entries;
}
public class ResponseEntry {
#JsonUnwrapped
private Entry entry;
}
pubic class Entry{
private Integer id;
private String value;
}

How to deserialize complex JSON to java object

I need to deserialize JSON to java class.
I have JSON like the following:
{
"data": {
"text": "John"
},
"fields":[
{
"id": "testId",
"name": "fieldName",
"options": {
"color": "#000000",
"required": true
}
},
{
"id": "testId",
"name": "fieldName1",
"options": {
"color": "#000000",
"required": false
}
}
]
}
and I need to deserialize this JSON (only "fields" section) to java class like the following:
public class Field {
public final String id;
public final String name;
public final String color;
public final boolean required;
}
and I need to get something like the following:
// The key is the id from field object (it can be the same in the multiple objects.)
Map<String, List<Field>> fields = objectMapper.readValue(json, Map<String, List<Field>>);
How can I do it using Jackson?
As long as jackson doesn't support #JsonWrapped, you have to use the following work around.
First you need to create a custom class which contains the fields:
public class Fields {
public List<Field> fields;
}
Depending on your ObjectMapper configuration you have to add #JsonIgnoreProperties(ignoreUnknown = true) to the Fields class, to ignore any other properties.
Next is that you have to define the nested Options class which is solely used temporarily:
public class Options {
public String color;
public boolean required;
}
And at last add this constructor to your Field class:
#JsonCreator
public Field(#JsonProperty("id") String id, #JsonProperty("name") String name, #JsonProperty("options") Options options){
this.id = id;
this.name = name;
this.color = options.color;
this.required = options.required;
}
The #JsonCreator annotation indicates to jackson that this constructor needs to be used for the deserialization. Also the #JsonProperty annotations are required as arguments to constructors and methods are not preserved in the bytecode
Then you can deserialize your json just like this:
List<Field> fields = objectMapper.readValue(json, Fields.class).fields;

convert List<Object> to List<HashMap<String,Double>> for a json array

I have a Json as shown below:-
{
"componentModelData": {
"modelNumber": "ABC",
"modelName": "",
"modelDescription": "",
"modelVendor": null,
"componentName": "HELO"
},
"revisionData": {
"revisionName": "rev12",
"revisionComment": "Comment",
"modifiedBy": "2323553"
},
"configData": [{"nodes":2085,"FxPOS-SX":16.5051,"FxPOS-SY":11.0479,"FxPOS-SZ":115.3421,"FxPOS-SXY":-13.8094,"FxPOS-SYZ":36.0105
},{"nodes":2085,"FxPOS-SX":16.5051,"FxPOS-SY":11.0479,"FxPOS-SZ":115.3421,"FxPOS-SXY":-13.8094,"FxPOS-SYZ":36.0105}]
}
DataMapping.java
#Getter
#Setter
#JsonSerialize
#NoArgsConstructor
#AllArgsConstructor
public class DataMapping implements Serializable {
/**
*
*/
private static final long serialVersionUID = -2635323042843403966L;
#JsonProperty("componentModelData")
#Valid
public ComponentModelDTO componentModelDTO;
#JsonProperty("revisionData")
#Valid
public RevisionDTO revisionDTO;
#JsonProperty("configData")
private List<Object> configData;
}
I want to convert the configData list to List<HashMap<String,Double>> list where String is the keys like 'nodes,FxPOS-SX,FxPOS-SY' and value is their respective values.
Can anyone please help me how can i achieve this?
Just replace List<Object> with List<Map<String,Double>>. Jackson will do the hard work for you:
#JsonProperty("configData")
private List<Map<String, Double>> configData;

Categories

Resources