JSON generic collection deserialization - java

I've such DTO classes written in Java:
public class AnswersDto {
private String uuid;
private Set<AnswerDto> answers;
}
public class AnswerDto<T> {
private String uuid;
private AnswerType type;
private T value;
}
class LocationAnswerDto extends AnswerDto<Location> {
}
class JobTitleAnswerDto extends AnswerDto<JobTitle> {
}
public enum AnswerType {
LOCATION,
JOB_TITLE,
}
class Location {
String text;
String placeId;
}
class JobTitle {
String id;
String name;
}
In my project there is Jackson library used for serialization and deserialization of JSONs.
How to configure AnswersDto (use special annotations) or AnswerDto (annotation as well) classes to be able to properly deserialize request with AnswersDto in its body, e.g.:
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"answers": [
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "LOCATION",
"value": {
"text": "Dublin",
"placeId": "121"
}
},
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "JOB_TITLE",
"value": {
"id": "1",
"name": "Developer"
}
}
]
}
Unfortunately Jackson by default maps value of AnswerDto object to LinkedHashMap instead of object of proper (Location or JobTitle) class type.
Should I write custom JsonDeserializer<AnswerDto> or configuration by use of #JsonTypeInfo and #JsonSubTypes could be enough?
To properly deserialize request with just one AnswerDto in form of
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "LOCATION",
"value": {
"text": "Dublin",
"placeId": "121"
}
}
I'm using:
AnswerDto<Location> answerDto = objectMapper.readValue(jsonRequest, new TypeReference<AnswerDto<Location>>() {
});
without any other custom configuration.

I've resolved issue by using Jackson's custom annotations #JsonTypeInfo and #JsonSubTypes:
public class AnswerDto<T> {
private String uuid;
private AnswerType type;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Location.class, name = AnswerType.Types.LOCATION),
#JsonSubTypes.Type(value = JobTitle.class, name = AnswerType.Types.JOB_TITLE)
})
private T value;
}

My suggestion is to make a separate interface for possible answer values and use #JsonTypeInfo on it. You can also drop type field from AnswerDto, AnswerType enum, and additional *AnswerDto classes becuse jackson will add type info for you. Like this
public class AnswerDto<T extends AnswerValue> {
private String uuid;
private T value;
}
#JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)
interface AnswerValue {}
class Location implements AnswerValue { /*..*/ }
class JobTitle implements AnswerValue { /*..*/ }
Resulting json will looks like this
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"answers": [
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"value": {
"#class": "com.demo.Location",
"text": "Dublin",
"placeId": "121"
}
},
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"value": {
"#class": "com.demo.JobTitle",
"id": "1",
"name": "Developer"
}
}
]
}
Which will be parsed using
AnswersDto answersDto = objectMapper.readValue(json, AnswersDto.class);
But this solution applies only in cases when you are a producer of json data and you do not have to think about backward compatibility.
In other cases you'll have to make custom desetializer for AnswersDto class.

Related

Deserializing complex json with matching objects by id (Jackson)

I have a proprietary API that return a complex JSON like:
{
"store": "store_name",
"address": "store_address",
"department": [
{
"name": "d1",
"type": "t1",
"items": [
"i1",
"i2"
]
},
{
"name": "d2",
"type": "t2",
"items": [
"i3"
]
}
],
"itemDescriptions": [
{
"id": "i1",
"description": "desc1"
},
{
"id": "i2",
"description": "desc2",
"innerItems": [
"i2"
]
},
{
"id": "i3",
"description": "desc3"
}
]
}
Is it possible to deserialize this JSON using Jackson into:
#AllArgsConstructor
class Store {
private final String store;
private final String address;
private final List<Department> departments;
/*some logic*/
}
#AllArgsConstructor
class Department {
private final String name;
private final String type;
private final List<Item> items;
/*some logic*/
}
#AllArgsConstructor
class Item {
private final String id;
private final String description;
private final List<Item> innerItems;
/*some logic*/
}
I tried to find answers, but find only this question without solution.
I know that I can do it in my code (deserialize as it is and create objects from result), but its very memory intensive (I have a lot of json and it can be large).
I know that I can write fully custom deserializer, but in this case, I have to describe the deserialization of each field myself - in case of some changes, I will have to change the deserializer, and not just the class(POJO/DTO).
Is there a way to do this with Jackson (or Gson) or with a minimal (preferably relatively generic) amount of my code?

JACKSON: How to ignore the POJO name while converting a POJO to JSON using Jackson?

I am using Jackson 2.10.1 library to convert my Java POJOs to JSON and I am getting the below output, I require the output without the POJO name(MyTestPojo here), I have tried various jackson annotations like #JsonIgnoreProperties but those are mostly for the members present in the POJO and not the POJO class name.
{
"MyTestPojo": [
{
"CreatedBy": "user1",
"Name": "testABC",
"UpdatedBy": null,
"UpdatedDate": null,
"IsActive": true,
"Value": "testABC1",
"CreatedDate": "2017-03-13 15:41:54.0",
"Description": "testABC"
},
{
"CreatedBy": "user2",
"Name": "testABC",
"UpdatedBy": null,
"UpdatedDate": null,
"IsActive": false,
"Value": "testABC2",
"CreatedDate": "2017-03-13 15:41:54.0",
"Description": "testABC"
}
]
}
whereas what I require is -
[
{
"CreatedBy": "user1",
"Name": "testABC",
"UpdatedBy": null,
"UpdatedDate": null,
"IsActive": true,
"Value": "testABC1",
"CreatedDate": "2019-03-13 15:41:54.0",
"Description": "testABC"
},
{
"CreatedBy": "user2",
"Name": "testABC",
"UpdatedBy": null,
"UpdatedDate": null,
"IsActive": false,
"Value": "testABC2",
"CreatedDate": "2020-03-10 15:41:54.0",
"Description": "testABC"
}
]
}
Is there a way to handle this with Jackson annotations?
The POJOs that I have used are-
#JacksonXmlRootElement(localName = "ArrayOfTestPojos")
public class GetResponseVO {
#JsonProperty("MyTestPojo")
#JacksonXmlProperty(localName = "MyTestPojo")
#JacksonXmlElementWrapper(useWrapping = false)
private ArrayList<MyTestPojo> MyTestPojoList;
public ArrayList<MyTestPojo> getMyTestPojoList() {
return MyTestPojoList;
}
public void setMyTestPojoList(ArrayList<MyTestPojo> MyTestPojoList) {
this.MyTestPojoList = MyTestPojoList;
}
// standard getters and setters
}
and
#JacksonXmlRootElement(localName = "MyTestPojo")
public class MyTestPojo {
#JsonProperty("Name")
private String name;
#JsonProperty("Description")
private String description;
#JsonProperty("IsActive")
private int isActive;
#JsonProperty("Value")
private String value = null;
#JsonProperty("CreatedBy")
private String createdBy;
#JsonProperty("CreatedDate")
private String createdDate;
#JsonProperty("UpdatedBy")
private String updatedBy;
#JsonProperty("UpdatedDate")
private String updatedDate;
// standard getters and setters.
}
```````````
I am also generating the XML out of this so you can ignore the annotations relevant to XML.
you can use JsonValue annotation for that purpose which basically "use-value of this property instead of serializing the container object". it can be used on getters also
#JsonValue indicates that results of the annotated "getter" method (which means signature must be that of getters; non-void return type, no args) is to be used as the single value to serialize for the instance. Usually value will be of a simple scalar type (String or Number), but it can be any serializable type (Collection, Map or Bean).
#JsonValue
#JacksonXmlProperty(localName = "MyTestPojo")
#JacksonXmlElementWrapper(useWrapping = false)
private ArrayList<MyTestPojo> MyTestPojoList;
But that would wrong practice as it will generate JSON like this, which would not be legal JSON.
{[{"x":"value"}, ...]}
If you want to alter only JSON structure (without affecting xml), you can use MixIn for that purpose.
public interface JsonMixin {
#JsonValue
List<MyTestPojo> getMyTestPojoList();
}
And register it with your object mapper and remove #JsonValue from the main Class.
objectMapper.addMixIn(GetResponseVO.class, JsonMixin.class);

Java Jackson deserialize objects of the same name but different class types

I have POJOs that are used as the request and response object in a REST API like so (I know duplicate #JsonProperty isn't syntactically correct, see below):
public class Request {
#JsonProperty("patient")
PatientObjectA patientA;
#JsonProperty("patient")
PatientObjectB patientB;
}
public class PatientObjectA {
#JsonProperty("identifier")
Private Identifier identifier
#JsonProperty("system")
Private String system;
#JsonProperty("value")
Private String value;
}
public class PatientObjectA {
#JsonProperty("identifier")
Private List<Identifier> identifier
#JsonProperty("system")
Private String system;
#JsonProperty("value")
Private String value;
}
There are minor differences in cardinality in that I want to be able to consume i.e the "Patient" object will sometimes be (PatientObjectA in Request class):
"patient": {
"identifier": {
"type": {
"coding": {
"system": "NA",
"code": "Patient"
},
"text": "Patient"
},
"system": "Patient",
"value": "000000000"
}
}
or this case (note the differences in cardinality on the identifier object, where in this case identifier can have one or more items) (PatientBObject in Request class):
"patient": {
"identifier": [{
"type": {
"coding": {
"system": "NA",
"code": "Patient"
},
"text": "Patient"
},
"system": "Patient",
"value": "3018572032"
}]
}
I would like to achieve a functionality where requests are mapped to the correct objects. Is there a way (other than a custom deserializer) where I can map the requests to the appropriate object by type/cardinality? Any insight would be appreciated!
Jackson support this with the #JsonTypeInfo annotation.
I recommend specifying the type info in a property (a json field) and use the full class name (as opposed to a short name) to provide a better guarantee of uniqueness:
#JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS, property = "jsonType")
public class PatientObjectA {
..
Output A looks like:
"patient": {
"jsonType": "com.company.PatientAObject"
"identifier": {
"type": {
"coding": {
"system": "NA",
"code": "Patient"
},
"text": "Patient"
},
"system": "Patient",
"value": "000000000"
}
}
Output B looks like:
"patient": {
"jsonType": "com.company.PatientBObject"
"identifier": {
"type": {
"coding": {
"system": "NA",
"code": "Patient"
},
"text": "Patient"
},
"system": "Patient",
"value": "000000000"
}
}
Note: Also, check out #JsonRootName, as it will give you the ability to create a 'rooted' json object without having to have that wrapper object you have.
#JsonRootName("Patient")
#JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS, property = "jsonType")
public class PatientObjectA {
..
.. and ..
#JsonRootName("Patient")
#JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS, property = "jsonType")
public class PatientObjectB {
..
Related terms to assist with more research:
polymorphism in json
json equivalent of xml namespaces.

#RequestBody mapping complex json object

I need to convert below json to java object of #RequestBody.
{
"entity": {
"id": 3,
"name": "james"
},
"conjunction": "OR",
"conditions": [
{
"operation": "equalTo",
"dataKey": "department",
"dataType": "string",
"value": "abc"
},
{
"operation": "notEqualTo",
"dataKey": "ID",
"dataType": "number",
"value": "100"
},
{
"operation": "notEqualTo",
"dataKey": "name",
"dataType": "strubg",
"value": "jack"
},
{
"operation": "between",
"dataKey": "END_DATE",
"dataType": "date",
"value1": "20180502",
"value2": "20180519"
}
]
}
The first three element in the array correspond to below java object.
public class ComparisonCondition extends Condition {
private String value;
}
The last element correspond below object.
public class BetweenCondition extends Condition {
private String value1;
private String value2;
}
They all inherit from below object.
public class Condition {
private String dataKey;
private String dataType;
private String operation;
}
The spring mvc method is below.
#RequestMapping(value = RequestAction.FILTER, method = RequestMethod.POST)
public List<Student> filter(
#RequestBody Filter<Student> filterConfig) {
return null;
}
The Filter object is below.
public class Filter<T> {
private String conjunction;
private T entity;
private List<Condition> conditions;
}
How can I map the json to java object successfully?
Currently it report "Could not read JSON: Unrecognized field "value" (class com.ssc.rest.entity.Condition), not marked as ignorable (3 known properties: "dataType", "dataKey", "operation"])
For your error, if the jackson parser don't know a field, it throws an exception.
You can avoid it by putting the annotation :
#JsonIgnore(ignoreUnknown=true)
on the target object.
For your mapping, I recommand to you to create an object corresponding to your json input, and then do manually your mapping to your target objects.
You are passing 4 variables in JSON for COndition
{
"operation": "equalTo",
"dataKey": "department",
"dataType": "string",
"value": "abc"
},
but your Java POJO has only 3 variables
public class Condition {
private String dataKey;
private String dataType;
private String operation;
}
just add value as well it will work fine.
Bottom line is : POJO class should have all the fields passed in JSON.
By the way your exception is telling same thing
Unrecognized field "value"
Edit 1:
I missed BetweenCondition and ComparisonCondition
You can define the Base Class in your case Condition with Sub Class property and hopefully it should work
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "javaclass")
#JsonSubTypes({
#Type(value = ComparisonCondition.class),
#Type(value = BetweenCondition.class)
})
public class Condition {
private String dataKey;
private String dataType;
private String operation;
}

Jackson Serialize without field name

This is slight extension of my previous question. So based on dambros' answer, the json now looks like:
"Foo": {
"title": {
"type": "title",
"value": "...",
"variable": "..."
},
"message": {
"type": "message",
"value": "...",
"variable": "..."
}
}
But what I really want is:
"Foo": [
{
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
}
]
Is there any way to write the Foo field as an array and also not display the variable names as fields (i.e remove "title" :).
That is not valid JSON, however this is:
{
"Foo": [
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
]
}
This is a JSON object with a single field named Foo that is an array of objects.
You should read the JSON spec.
Alternatively, if you have a List<Foo>, you can serialize the list directly, giving you a JSON array as the root, instead of a JSON object as the root:
[
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
]
It seems that what you're trying to accomplish is to represent your java object in a way that you can send the object type and fields. Under that assumption, I'd try to get away from manual serialization. Just create a DTO with the format that you need, that you can populate with the domain objects you have. This would be an example:
public class FooSerialization {
public static class Foo {
private String title;
private String message;
}
public static class Foo2 {
private String value;
private String variable;
}
public static class ClassDTO {
private String type;
private List<FieldDTO> fields;
}
public static class FieldDTO {
private String type;
private String value;
private String fieldName;
}
public static void main(String[] args) throws JsonProcessingException {
Foo2 foo2 = new Foo2();
foo2.setValue("valueMessage");
foo2.setVariable("variableMessage");
Foo foo = new Foo();
foo.setMessage("messageMessage");
foo.setTitle("titleMessage");
ObjectMapper mapper = new ObjectMapper();
List<ClassDTO> dtos = new ArrayList<ClassDTO>();
dtos.add(convert(foo));
dtos.add(convert(foo));
System.out.println(mapper.writeValueAsString(dtos));
}
private static ClassDTO convert(Object obj) {
ClassDTO dto = new ClassDTO();
dto.setType(obj.getClass().getSimpleName());
List<FieldDTO> fieldDTOs = new ArrayList<FieldDTO>();
dto.setFields(fieldDTOs);
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
FieldDTO fieldDto = new FieldDTO();
try {
fieldDto.setFieldName(field.getName());
fieldDto.setValue(field.get(obj).toString());
fieldDto.setType(field.getType().getSimpleName());
fieldDTOs.add(fieldDto);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return dto;
}
}
Getters and Setters are omitted for simplicity. This basically converts from Foo or Foo2 to certain ClassDTO, that contains type and a list of FieldDTOs that have the field details.
Output looks like this:
[
{
"type": "Foo",
"fields": [
{
"fieldName": "title",
"type": "String",
"value": "titleMessage"
},
{
"fieldName": "message",
"type": "String",
"value": "messageMessage"
}
]
},
{
"type": "Foo2",
"fields": [
{
"fieldName": "value",
"type": "String",
"value": "valueMessage"
},
{
"fieldName": "variable",
"type": "String",
"value": "variableMessage"
}
]
}
]
It looks to me that you can solve lots of problems if you can use something like this:
#JsonFormat(shape=JsonFormat.Shape.ARRAY)
public static class Foo {
#JsonProperty public Foo1 title;
#JsonProperty public Foo2 message;
}
#JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({#JsonSubTypes.Type(value = Foo1.class, name = "title"),
#JsonSubTypes.Type(value = Foo2.class, name = "message")})
public static class FooParent{
#JsonProperty private String value;
#JsonProperty private String variable;
}
public static class Foo1 extends FooParent{}
public static class Foo2 extends FooParent{}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Foo foo = new Foo();
foo.title = new Foo1();
foo.message = new Foo2();
String serialized = mapper.writeValueAsString(foo);
System.out.println(serialized);
}
Result is:
[
{"type":"title","value":null,"variable":null},
{"type":"message","value":null,"variable":null}
]
Read following blog json in java
This post is a little bit old but still i want to answer you Question
Step 1: Create a pojo class of your data.
Step 2: now create a object using json.
Foo foo = null;
ObjectMapper mapper = new ObjectMapper();
try{
foo = mapper.readValue(newFile("/home/sumit/foo.json"),Foo.class);
} catch (JsonGenerationException e){
e.printStackTrace();
}
For further reference you can refer following link
Thanks

Categories