Deserializing nested / recursive JSON into Java objects with inheritance - java

I have a json which is complex/nested. My json file consists of two equivalent java objects. One is Complex_Expression and another is Simple_Expression.
Complex_Expression is in the following form:
{
"SomeOpearator":0,
"ASpecificKey":1, //the value 1 is fixed for complex expression.
"Expressions":[ ] //array of one or more expressions
}
Simple Expression is in the following form:
{
"Operand":"Some String Value",
"ASpecificKey":0, //the value 0 is fixed for simple expressions.
"SomeComparisionOpearator":1, // enums which associates int to different comparison operators.
"Value":["String1"] //this is an array of strings.
}
The Expressions in turn can have Complex_Expression and/or Simple_Expression. The json file always starts from Complex_Expression. I have to deserialize this JSON file. My final goal is to make an expression usingComplex_Expression and Simple_Expression objects and with some logics in these classes. I don't mind using jackson or gson or maybe other dependencies.
Till now I have created a base class called Expression. Complex_Expression and Simple_Expression both inherits this class. Then I started writing Custom Json Deserializer. But in the custom deserializer I am stuck and I don't know how should I proceed. Please help me on this. My Simple_Expression class looks like this and somewhat similar is the Complex_Expression class.
public class Simple_Expression extends Expression
{
#JsonProperty("Operand") //use jackson deserializer for this class.
public String Operand;
#JsonProperty("SomeComparisionOpearator")
public SomeComparisionOpearator someCompareOperator;
#JsonProperty("Value")
public Object value;
public Simple_Expression()
{
super(ASpecificKey.Simple); //Simple corresponds to 0
}
}
Update
Some more description about my input and output. With input given a JSON string like this:
{
"SomeOpearator": 0,
"ASpecificKey": 1,
"Expressions": [
{
"SomeOpearator": 1,
"ASpecificKey": 1,
"Expressions": [
{
"Operand": "People",
"ASpecificKey": 0,
"SomeComparisionOpearator": 14,
"Value": [
"Rich"
]
}
]
},
{
"SomeOpearator": 1,
"ASpecificKey": 1,
"Expressions": [
{
"Operand": "Grade",
"ASpecificKey": 0,
"SomeComparisionOpearator": 2,
"Value": [
"Grade A"
]
}
]
}
]
}
I should be able to do something like this, assuming jackson deserializer:
ObjectMapper mapper = new ObjectMapper();
Expression myExpressionObject = mapper.convertValue(jsonString, Expression.class);
It should give me the deserialized object into the myExpressionObject which will consists of a list of expressions (Arraylist or Array, no problem).

This would be easy with Gson extras RuntimeTypeAdapterFactory because you have the field ASpecificKey that can be used as type discriminator. See this for usage. You can just copy the source to your project if you already have Gson included.
I took a liberty to fix your Java naming convention so classes actually look like (and also your JSON should be fixed to correct convention):
#Getter #Setter
public class Expression {
private int aSpecificKey;
}
#Getter #Setter
public class SimpleExpression extends Expression {
public SimpleExpression() {
setASpecificKey(0);
}
private String operand;
private int someComparisonOperator;
private String[] values;
}
#Getter #Setter
public class ComplexExpression extends Expression {
public ComplexExpression() {
setASpecificKey(1);
}
private String someOperator;
private Expression[] expressions;
}
Against this kind of DTOs you could just instantiate a specific RuntimeTypeAdapterFactory:
final RuntimeTypeAdapterFactory<Expression> expressionTypeFactory =
RuntimeTypeAdapterFactory
.of(Expression.class, "aSpecificKey")
.registerSubtype(SimpleExpression.class, "0")
.registerSubtype(ComplexExpression.class, "1")
Then deserializing would be like:
Expression e = getGson(expressionTypeFactory)
.fromJson(getPackageResourceReader(YOUR_NOTATION_FIXED_JSON),
Expression.class);
Note that Gson will not by default deserialize the type discriminator aSpecificKey that is why there is default constructors that set that. If you do not need it you can remove the default constructors.

Related

Can I have an Avro Schema with an enum that includes values as enum members?

This is the Java enum that I want to transform into an Avro Schema:
public enum ApplicationCode {
APP_A("MY-APP-A"),
APP_B("MY-APP-B");
private final String code;
ApplicationCode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Since enums are generally available as Types in Avro, I came up with following:
{
"type" : "enum",
"name" : "ApplicationCode",
"namespace" : "com.example",
"symbols" : [ "APP_A", "APP_B" ]
}
And referenced it in my main Avro like this:
"fields": [
{
"name": "masterApplicationCode",
"type": "ApplicationCode"
},
It works like that but unfortunately I am losing the Application Codes (e.g. "MY-APP-A") using this approach. I'm looking for something, that allows me to include both, the code and the label. Something like
{
"type" : "enum",
"name" : "ApplicationCode",
"namespace" : "com.example",
"symbols" : [ "APP_A("MY-APP-A")", "APP_B("MY-APP-B")" ]
}
Is it even possible to have this kind of complex enums or is there any workaround to achieve this?
I believe the avro schema is internally transforming it into a JSON String. So, I think the question is more about serializing enums. Reference from here - https://www.baeldung.com/jackson-serialize-enums
I think it should return the code if you use JsonFormat annotation like this -
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ApplicationCode {
Otherwise you will need to add a Custom Serializer for enum.
I solved my problem by writing custom serializer / deserializer that map an object with complex typed fields to one that is being sent with e.g. Strings instead of enums.
Here an example of the custom serializer:
public class CustomSerializer implements Serializer<ApplicationObject> {
#Override
public byte[] serialize(String topic, ApplicationObject ApplicationObjectDto) {
com.example.avro.ApplicationObject applicationObject = com.example.avro.ApplicationObject.newBuilder()
.setApplicationCode(ApplicationObjectDto.getApplicationCode().getCode())
.build();
return SerializationUtils.serialize(applicationObject);
}
}

Mapped Complex Map Type API Response To Java POJO

Here is my API Response, here the response is coming as Map object not Json.
{
"staticResponses": [
{
"code": {
"id": "someId",
"value": "44343567"
},
"staticAttributes": [
{
"id": "SEC_GUAR",
"value": "someValue4"
},
{
"id": "FIN_BOND_TYPE",
"value": ""
},
{
"id": "SEC_ISSER_ID",
"value": "someValue5"
},
{
"id": "SEC_ISSE_CRNCY",
"value": "someValue6"
}
//Here more objects with id and value(same as above) which needs to be mapped to corresponding fields of RatingModel Pojo.
]
}
]
}
API Response to equivalent Pojo field mapping
Keys Comming In API Response Corresponding Field Name in Model
SEC_GUAR guarantor
FIN_BOND_TYPE covered
SEC_ISSER_ID issuerId
SEC_ISSE_CRNCY securityCurrency
My Pojo to which I need to mapped the data
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class RatingCompositionModel implements CompositionModel {
private List<RatingModel> ratings;
}
RatingModel.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class RatingModel implements CompositionModel, Serializable {
private static final long serialVersionUID = -8447345872052120852L;
private String securityCurrency;
private String covered;
private String guarantor;
private String issuerId;
//here renaming fields
}
Code that I have written to achieve the same
private CompositionModel mapResponseToObject(LinkedHashMap responseBody) {
RatingCompositionModel ratingCompositionModel = new RatingCompositionModel();
List<RatingModel> ratingModelList = new ArrayList<>();
List<LinkedHashMap> responseObjectList = (List<LinkedHashMap>) responseBody.get("staticResponses");
if( null != responseObjectList && !responseObjectList.isEmpty() ) {
responseObjectList.forEach(responseObject -> {
List<LinkedHashMap> staticAttributes = (List<LinkedHashMap>)responseObject.get("staticAttributes");
if( null != staticAttributes && !staticAttributes.isEmpty()) {
RatingModel ratingModel = new RatingModel();
staticAttributes.forEach(staticAttribute -> {
if( ((String)staticAttribute.get("id")).equals("SEC_GUAR") ) {
ratingModel.setSecurityCurrency((String)staticAttribute.get("value"));
}
// more else if here...
});
ratingModelList.add(ratingModel);
}
});
}
ratingCompositionModel.setRatings(ratingModelList);
return ratingCompositionModel;
}
So here the problem is number of if-else block that I have to used,currently I have around 50 fields which I need to extract from the API Response and need to mapped to the corresponding fields in my POJO, so with my approach I have to use 50 if-else conditions and in future, if any change in my POJO like addition of some more fields(which has a high possibility) I have to add more if-else block.
Also worth noting here I cannot change the name of the fields in API response to match it with my POJO because this is some third party API not in my control.
Here looking for more dynamic solution to avoid if-else block as well to efficiently handle any future changes.
you don't need to have if-else conditions their, just iterate through staticAttributes and store them in a Map and once that is done, you can directly map this Map object to your POJO class using ObjectMapper's convertValue api.
FinalPojo readValue = objectMapper.convertValue(collect, FinalPojo.class);
But you would need to tell Jackson that which key in the map is to be mapped to what field in your POJO. And you can use #JsonProperty annotation for that.
Below is the sample POJO class
import com.fasterxml.jackson.annotation.JsonProperty;
public class FinalPojo {
#JsonProperty("SEC_GUAR")
private String guarantor;
#JsonProperty("FIN_BOND_TYPE")
private String covered;
#JsonProperty("SEC_ISSER_ID")
private String issuerId;
#JsonProperty("SEC_ISSE_CRNCY")
private String securityCurrency;
}
you can simply annotate the fields of the class with #SerializedName annotation given by Gson library. you can find an example below. Its really conveniet to use this librart to convert to/from json.
https://www.tutorialspoint.com/what-to-use-serializedname-annotation-using-gson-in-java

Deserialize embedded JSON-String as typed Object

Given these to models (using Lombok #Data for simplicity)
#Data
public class RootModel {
private Integer myRootProperty;
private SubModel mySubModel;
}
#Data
public class SubModel {
private Integer mySubProperty
}
and this JSON-String:
{
"myRootProperty" : 5,
"mySubModel" : "{ "mySubProperty" : 3 }"
}
Is it possible (via Jackson-Annotations) to directly deserialze the embedded JSON-String (which origins from a DB-Column) to its Java-POJO-Model?
Background is that we need a JSON-Formatted configuration in our DB and I want to handle it typesafe as soon as possible - ideally directly after deserialization.
Yes, it sure is. Just annotate with the #JsonProperty tag
#Data
public class RootModel {
#JsonProperty("myRootProperty")
private Integer myRootProperty;
#JsonProperty("mySubModel")
private SubModel mySubModel;
}
#Data
public class SubModel {
#JsonProperty("mySubProperty")
private Integer mySubProperty
}
Then use the object mapper to deserialise into the POJO
RootModel rootModel = objectMapper.readValue(jsonString, RootModel.class);
The problem lies with your Json string. If you take a look at your Json,
{
"myRootProperty" : 5,
"mySubModel" : "{ "mySubProperty" : 3 }"
}
The subnode is enclosed in "" like "{ "mySubProperty" : 3 }" which will be considered as it is a value.
Instead if your Json is like as specified below (Note:I only removed the double quotes) like { "mySubProperty" : 3 }, deserialization won't complain any more. Your typical Json string would look like specified below.
{
"myRootProperty" : 5,
"mySubModel" : { "mySubProperty" : 3 }
}
No there is no automated way to do that. You could try a custom deserializer. Otherwise read it as just string and convert that to Typed Object in another step.

How to use dynamic json value on my POJO class with Gson?

{
"localeCode": "",
"map": {
"DynamicName1": [],
"DynamicName2": [
{
"date": "2016-05-15T00:00:00",
"seqId": 1,
"status": 10
},
{
"date": "2016-05-16T00:00:00",
"seqId": 83,
"status": 10
}
],
"DynamicName3": [],
"DynamicName4": []
},
"respCode": 100,
"respMsg": "success",
"status": 1
}
How to correctly map this kind of json. If you can see that, Dynamic is a dynamic name. So far I have done this :
public class MapModel {
public MapObject map;
public static class MapObject{
public java.util.Map<String, Student> queryStudent;
public static class Student{
public String date;
public String seqId;
public String status;
}
}
}
But when run the app. I'm getting NullPointerException. Can somebody help me?
You're getting the NullPointerException accessing queryStudent of your MapObject inside your MapModel since it's not correctly filled when you're trying to deserialize your Json.
So to solve your problem look at Gson documentation where you can see:
You can serialize the collection with Gson without doing anything
specific: toJson(collection) would write out the desired output.
However, deserialization with fromJson(json, Collection.class) will
not work since Gson has no way of knowing how to map the input to the
types. Gson requires that you provide a genericised version of
collection type in fromJson(). So, you have three options:
Use Gson's parser API (low-level streaming parser or the DOM parser
JsonParser) to parse the array elements and then use Gson.fromJson()
on each of the array elements.This is the preferred approach. Here is
an example that demonstrates how to do this.
Register a type adapter for Collection.class that looks at each of the
array members and maps them to appropriate objects. The disadvantage
of this approach is that it will screw up deserialization of other
collection types in Gson.
Register a type adapter for MyCollectionMemberType and use fromJson()
with Collection.
Since your MapObject containts a java.util.Map but your class itself it's not generic, I think that a good approach for your case is create a Deserializer.
Before this try to clean up your class definition, to provide constructors to make the deserializer easy to build. Your POJO classes could be:
Student class
public class Student{
public String date;
public String seqId;
public String status;
public Student(String date, String seqId, String status){
this.date = date;
this.seqId = seqId;
this.status = status;
}
}
MapObject class
Note: I change you Map definition, since in your Json seems that could be multiple students for each DynamicName (look at DynamicName2 from your question), so I use Map<String,List<Student>> instead of Map<String,Student>:
public class MapObject{
public Map<String,List<Student>> queryStudent;
public MapObject(Map<String,List<Student>> value){
this.queryStudent = value;
}
}
MapModel class
public class MapModel {
public MapObject map;
}
Now create a Deserializer for your MapObject:
public class MapObjectDeserializer implements JsonDeserializer<MapObject> {
public MapObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Map<String,List<Student>> queryStudents = new HashMap<String,List<Student>>();
// for each DynamicElement...
for (Map.Entry<String,JsonElement> entry : json.getAsJsonObject().entrySet()) {
List<Student> students = new ArrayList<Student>();
// each dynamicElement has an Array so convert and add an student
// for each array entry
for(JsonElement elem : entry.getValue().getAsJsonArray()){
students.add(new Gson().fromJson(elem,Student.class));
}
// put the dinamic name and student on the map
queryStudents.put(entry.getKey(),students);
}
// finally create the mapObject
return new MapObject(queryStudents);
}
}
Finally register the Deserializer and parse your Json:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MapObject.class, new MapObjectDeserializer());
Gson gson = builder.create();
MapModel object = gson.fromJson(YourJson,MapModel.class);
DISCLAIMER: For fast prototyping I test this using groovy, I try to keep the Java syntax but I can forget something, anyway I think that this can put you on the right direction.
Hope it helps,

Exception happened when deserialize json based on class, the class have an interface type inside

I was given json file and third party class:Dealer and interface IDealerAttributes (I can not change either of them);
(I remove package name and imports to make the code simple)
JSON file
{
"serviceURL": "com.mycompany.serviceURL",
"dealerAttributes": [
{
"language": "language0",
"dealerAttributeName": "dealerAttributeName0",
"updateDate": 0
},
{
"language": "language1",
"dealerAttributeName": "dealerAttributeName1",
"updateDate": 1
}
]
}
class Dealer {
private String serviceURL;
private List dealerAttributes;
public Dealer() {
dealerAttributes = new ArrayList();
}
//Getters and Setters...
}
public interface IDealerAttributes {
public String getLanguage();
public String getDealerAttributeName();
public long getUpdateDate();
}
once I use:
gson.fromJson(jsonObj.toString(), Dealer.class);
I will get exception from this line:
Exception unmarshalling json String into Object: com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#60e26ffd failed to deserialize json object [{"language":"language0","dealerAttributeName":"dealerAttributeName0","updateDate":0},{"language":"language1","dealerAttributeName":"dealerAttributeName1","updateDate":1}] given the type java.util.List
How can I read this json file based on Dealer.class, IDealerAttributes?
But I can add one class, let's say:
public class DealerAttributes implements IDealerAttributes {
private String language;
private String dealerAttributeName;
private long updateDate;
public DealerAttributes() {
}
//Getters and Setters...
}
Since I am new to json/gson, would you please give detailed instruction to help me out? Thanks.
[added]
Consider if there are 100 fields in Dealer class, and there are another 100 interface used/nested in Dealer. I am thinking whether anyone have experience using this way: (MyType is interface)
gson.registerTypeAdapter(MyType.class, new MyType());`
You could map it to a List of Maps and then use a BeanMapper like http://code.google.com/p/orika/ to get some more informative error messages

Categories