GSON serialize a list of polymorphic Composite classes - java

I have a set of classes with following structure
public abstract class Rule { .... }
public class RuleType1 extends Rule { ... }
public class RuleType2 extends Rule { ... }
public class RuleType3 extends Rule { ... }
public class RuleType4 extends Rule { ... }
public class RuleSet extends Rule {
...
private final Collection<Rule> rules;
}
I want to parse following json, which contains multiple nested Rule child classes. Since RuleSet contains Collection of Rule, it can have multiple RuleSet as well.
I have checked these answers already. I don't want to implement custom (de)serializer because that will make child classes less flexible. (Whoever will change classes will need to change serializer classes as well)
Deserializing an abstract class in Gson
Gson serialize a list of polymorphic objects
Here is the same json, I want to serialize/deserialize.
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType1"
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType2"
},
{
"ruleType": "RuleType3"
}
]
}
]
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType1"
},
{
"ruleType": "ruleSet",
"rules": [
{
"ruleType": "RuleType3"
},
{
"ruleType": "RuleType4"
}
]
}
]
}
]
}
Edit after comments:
This is my class structure where RuleSet has Collection<IRule> rules
And this is my class
public class SampleClass {
public IRule parameters;
public double defaultConfidence;
public String ruleType;
}
static RuntimeTypeAdapterFactory<IRule> adaptor = RuntimeTypeAdapterFactory.of(IRule.class)
.registerSubtype(RuleSet.class)
.registerSubtype(Rule.class);
static RuntimeTypeAdapterFactory<Rule> ruleAdaptor = RuntimeTypeAdapterFactory.of(Rule.class)
.registerSubtype(DocumentTypesRule.class)
.registerSubtype(DataAttributesRule.class)
.registerSubtype(DictionariesRule.class)
.registerSubtype(FileFormatsRule.class)
.registerSubtype(FileLengthRule.class)
.registerSubtype(FileNamePatternsRule.class)
.registerSubtype(FixedRule.class)
.registerSubtype(ImageTypesRule.class)
.registerSubtype(KeywordsRule.class)
.registerSubtype(RegexesRule.class)
.registerSubtype(SimilaritiesRule.class)
.registerSubtype(TopicsRule.class);
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapterFactory(adaptor)
.registerTypeAdapterFactory(ruleAdaptor)
.create();
and facing following error while calling GSON.fromJson(response, SampleClass.class)
java.lang.RuntimeException: Unable to invoke no-args constructor for interface <classPath>.IRule. Registering an InstanceCreator with Gson for this type may fix this problem.

Related

Jackson: Deserializing a polymorphic list

I would like to implement a custom deserializer for our REST API that is not only used by Java application. Therefore I don't want to have Jackson putting type information into the serialized JSON.
I'm currently struggling with deserializing CollectionExpand since it contains a list data of specific ResourceModel.
public class EntityModel<R extends ResourceModel> implements Serializable {
private R data;
private List<ResourceLink> links;
private List<CollectionExpand> expands;
}
public class CollectionExpand {
private String name;
// Resource Model is an interface
private Collection<ResourceModel> data;
}
ResourceModel is an interface an each CollectionExpand contains a collection of one type of ResourceModel per name.
For example a json output could look like this.
{
"data": {},
"links": [],
"expand": [
{
"name": "photos",
"data": [
{
"id": 12,
"name": "hello.jpg"
},
{
"id": 12,
"name": "hello.jpg"
}
]
},
{
"name": "persons",
"data": [
{
"id": 783378,
"name": "Peter",
"age": 12
},
{
"id": 273872,
"name": "Maria",
"age": 77
}
]
}
]
}
As you can see each name contains the same type of resource model. photos contains PhotoResourceModel and person contains PersonResourceModel.
I started to implement my custom Jackson Deserializer
public class CollectionExpandDeserializer extends StdDeserializer<CollectionExpand> {
public CollectionExpandDeserializer() {
super(CollectionExpand.class);
}
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
CollectionExpand collectionExpand = new CollectionExpand();
if (Objects.equals(p.nextFieldName(), "name")) {
collectionExpand.setName(p.nextTextValue());
}
if (Objects.equals(p.nextFieldName(), "data")) {
// depending on the field name I would like to delegate the deserialization to a specific type.
if (name.equals("photos") {
// how to do this?
collectionExpand.setData(/* deserialize to a list of PhotoResource */);
}
}
return collectionExpand;
}
I'm current stuck on how can I delegate telling Jackson to deserialize this as a PhotoResource list.
In general is this the right approach or is there another way to do it (without putting any Jackson meta data into the JSON while serialization)?
I have ended up implementing my custom deserializer as below
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonNode node = ctx.readTree(p);
CollectionExpand collectionExpand = new CollectionExpand();
collectionExpand.setName(node.get("name").asText());
ArrayNode data = node.withArray("data");
Iterator<JsonNode> iterator = data.iterator();
while (iterator.hasNext()) {
Class<? extends ResourceModel> aClass = resolveClass(collectionExpand.getName());
if (aClass != null) {
JsonNode jsonNode = iterator.next();
collectionExpand.getData().add(p.getCodec().treeToValue(jsonNode, aClass));
}
}
return collectionExpand;
}
private Class<? extends ResourceModel> resolveClass(String name) {
if ("contents".equals(name)) {
return ContentsGetResourceModel.class;
} else if ("tags".equals(name)) {
return TagsGetResourceModel.class;
} else {
return null;
}
}
I took my a while to understand how to deserialize a JsonNode/TreeNode into a specific type. In the end I learned that this can be basically done by using the parsers codec.
PhotoResource photoResource = p.getCodec().treeToValue(jsonNode, PhotoResource.class);

Parse a nested dynamic module using Gson

I have a JSON that looks more or less like this:
{
"modules": {
"someExistingModule": {
"name": "pug",
...
},
"randomExistingModule": {
"type": "cat",
...
},
"myNewModule": { // <----- I care about this module. Note that this is NOT an array
"modules": {
"img1": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img2": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img3": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"txt1": { // <------ Updated JSON
"type": "text",
"content": "Hello world 1"
},
"txt2": {
"type": "text",
"content": "Hello world 2"
},
...
}
}
Inside myModule there can be N number of imgN objects and txtN. I need to parse this dynamically.
My current Response class looks like this:
public class MyModuleResponse extends SomeResponseClass
{
#Override
public void parse(InputStream data)
{
T responseBody = readJsonStream(data, MyModuleResponseBody.class());
MyModuleDataParser.parse(responseBody);
}
MyModuleDataParser.java
...
public static MyModuleDataParser parse(#Nullable MyModuleResponseBody body)
{
parseSomeExistingModule();
parseRandomExistingModule();
parseMyNewModule(); // <--- This is the new module I'm trying to parse. Currently, this method is empty.
}
MyModuleResponseBody.java
public class MyModuleResponseBody
{
public Modules modules;
public static class Modules
{
SomeExistingModule someExistingModule;
RandomExistingModule randomExistingModule;
MyNewModule myNewModule; // <----- My new module
}
public static class SomeExistingModule
{
String name;
...
}
public static class RandomExistingModule
{
String type;
...
}
public static class MyNewModule
{
public ??? modules; // <--- Trying to define the Type here. Something like List<MyImageModule>. But, it won't work
}
MyImageModule.java
public class MyImageModule extends Module // <---- Update: This class now extends a generic Module class
{
private String url;
private String altText;
}
MyTextModule.java <---- New Module
public class MyTextModule extends Module // New class
{
private String content;
}
Module.java
public class Module // <----- New parent class
{
protected String type;
}
How do I create a list of MyImageModule from myNewModule? I believe I need to use some kind of TypeAdapter from Gson library. But, I'm not familiar how to do this inside an existing response.
Use Map<String, MyImageModule>, in fact, a hashmap to solve the issue of non-list modules object in the json.
public static class MyNewModule {
public Map<String, MyImageModule> modules; // initialize as a hashmap
}

How to specialize codec to deserialize top level class with parametrized type?

I have a MongoDB Aggregation Pipeline with $facet to paginate results. The result is two fields : totalRows (Integer) and currentPage (Array).
[
{
"$match" : {
"$and" : [
{
"dateTraitement" : {
"$gte" : ISODate("2018-01-01T01:00:00.000+01:00")
}
},
{
"dateTraitement" : {
"$lte" : ISODate("2020-02-27T01:00:00.000+01:00")
}
}
]
}
},
{
"$facet" : {
"currentPage" : [
{
"$skip" : 0
}
],
"totalRows" : [
{
"$count" : "count"
}
]
}
},
{
"$unwind" : "$totalRows"
},
{
"$project" : {
"currentPage" : 1,
"totalRows" : 1
}
}
]
I want to map this result in a Pojo :
#Data
public class PaginatedResults<T> {
private List<T> currentPage;
private Integer totalCount;
}
If I just try to use aggregate without customizing the registry (Ì€collection.aggregate(pipeline, PaginatedResults.class)), I have the following error :
PaginatedResults contains generic types that have not been specialised.
Top level classes with generic types are not supported by the PojoCodec.
How can I instruct the codec to use a specific class to deserialize the property currentPage in PaginatedResults as a List<MyPojo> ?
You can register a custom converter. Reference documentation
#Configuration
public class Converters {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(
Arrays.asList(
new PaginatedResultsConverter()));
}
}
#ReadingConverter
public class PaginatedResultsConverter implements Converter<Document, PaginatedResults<T>> {
#Override
public PaginatedResults convert(Document source) {
//implement PaginatedResults manually here
}
}

Using jackson deserialising a property which can be List of object or the object

My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}

Dynamically create an object based on a field

I receive from a network API a JSON like this one:
{
...
"foobar":
{
"type": "...",
"keyTypeA": "value"
}
}
With foobar having different object types depending on its type field.
So, foobar can be:
{
"type": "typeA",
"keyA1": "...",
"keyA2": "...",
...
}
or
{
"type": "typeB",
"keyB1": "...",
"keyB2": "...",
...
}
etc.
How can I parse these JSON models into my POJO classes defined here:
public class FoobarTypeBase {
#SerializedName("type")
public String type;
}
public class FoobarTypeA extends FoobarTypeBase {
#SerializedName("keyA1")
public SomeObject keyA1;
#SerializedName("keyA2")
public SomeObject keyA2;
}
public class FoobarTypeB extends FoobarTypeBase {
#SerializedName("keyB1")
public SomeObject keyB1;
#SerializedName("keyB2")
public SomeObject keyB2;
}
I guess I have to deal with TypeAdapterFactory and TypeAdapter but I don't known how to do it efficiently.
I usually use a combination of Retrofit + Gson and do it this way:
RuntimeTypeAdapterFactory<FoobarTypeBase> itemFactory = RuntimeTypeAdapterFactory
.of(FoobarTypeBase.class, "type") // The field that defines the type
.registerSubtype(FoobarTypeA.class, "foobar")
.registerSubtype(FoobarTypeB.class) // if the flag equals the class name, you can skip the second parameter.
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(itemFactory)
.create();
Then I initialize Retrofit like this:
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(BASE_URL);
builder.addConverterFactory(GsonConverterFactory.create(gson));
Retrofit retrofit = builder.build();

Categories