Deserialize JSON Array with random name - GSON - java

Am trying to deserialize a complex JSON structure using GSON. The API provider complicates things by providing an array in the results with a random name.
This is the (simplified/generified) JSON:
{
"field_1": "value",
"field_2": "value",
"field_3": {
"RANDOM_NAME": [
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
},
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
}
]
},
"field_4": "value"
}
and this is the corresponding (highly simplified) POJO:
public class responseObject {
String field_1;
String field_2;
Field3 field_3;
String field_4;
class Field3{
ArrayObject[] arrayObjects;
}
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
However, when i run responseObject response = new Gson().fromJson(getJSON(),responseObject.class); i get the following call stack:
indicating that field_3 was not properly deserialized and does not contain an array of ArrayObject.
In this post the answers reference how to convert the data to a map, but in my case the data structure of each item in the array is actually much larger than this simplified example, and it defeats the purpose of using GSON if i have to manually pick the data i need out of a complex list of nested maps. also having trouble getting these answers to work in my scenario where the random object is an array an not a plain json object.
how do i get the randomly named array in the JSON to properly deserialize into the variable responseObject.Field3.arrayObjects??

You can avoid the complexity of using a TypeAdapeter by making the type of field_3 Map<String, List<ArrayObject>>
public class responseObject {
String field_1;
String field_2;
Map<String, List<ArrayObject>> field_3;
String field_4;
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
And then to get the first item out of the Map without knowing its key you can use:
public List<ResponseObject.ArrayObject> getFirstValue(Map<String, List<ResponseObject.ArrayObject>> field_3) {
return field_3.values().iterator().next();
}

This can be solved by writing a custom TypeAdapter for Field3 which ignores the name of the property and only reads the value. The TypeAdapter has to be created by a TypeAdapterFactory to allow getting the delegate adapter for ArrayObject[]:
class Field3TypeAdapterFactory implements TypeAdapterFactory {
public Field3TypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only support Field3 class
if (type.getRawType() != Field3.class) {
return null;
}
TypeAdapter<ArrayObject[]> fieldValueAdapter = gson.getAdapter(ArrayObject[].class);
// Cast is safe, check at beginning made sure type is Field3
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<Field3>() {
#Override
public void write(JsonWriter out, Field3 value) throws IOException {
throw new UnsupportedOperationException("Serialization is not supported");
}
#Override
public Field3 read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
// Skip the random property name
in.skipValue();
ArrayObject[] fieldValue = fieldValueAdapter.read(in);
in.endObject();
Field3 object = new Field3();
object.arrayObjects = fieldValue;
return object;
}
};
return adapter;
}
}
You can then either register the factory with a GsonBuilder, or you can annotate your Field3 class with #JsonAdapter. When using #JsonAdapter the factory class should have a no-args constructor.

Related

GSON - print all fields that are annotated as deserialized=true

I have a Java EE project that is using GSON library (Google's library for processing of JSON objects).
In my entity classes I use #Expose annotation to control which fields are considered by GSON. I also use serialize/deserialize properties on that annotation to control which fields are considered when serializing a Java object to JSON and which fields are considered when deserializing JSON objects to Java objects. For example:
public class Movie {
#Expose(serialize=true, deserialize=false)
#Id
#GeneratedValue
private long id;
#Expose(serialize=true, deserialize=true)
private String name;
#Expose(serialize=true, deserialize=true)
private String genre;
#Expose(serialize=false, deserialize=true)
private String secretID;
}
Here when I send the JSON object to be deserialized into Java object I send an object like this:
{
"name": "Memento",
"genre": "thriller",
"secretID": "123asd"
}
And, when I serialize Java object to JSON I get something like this:
{
"id": 1,
"name": "Memento",
"genre": "thriller"
}
I have this Java code:
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
String json = gson.toJson(new Movie());
System.out.println(json);
}
that generates this as it's output:
{
"id": 0,
"name": "",
"genre": ""
}
Those are fields that are marked to be serialized. However, what if I need to print out all of the fields that are marked to be deserialized, so that I can easier create a JSON object that will be used as input when creating new Movies.
The desired output is this:
{
"name": "",
"genre": "",
"secretID": ""
}
Note: I don't want to change serialize/deserialize properties on #Expose annotations because they are set to how my application needs to work. I just need an easy way to generate a template JSON objects that will be used as input to my application, so I don't have to type it manually.
You could implement more generic ExclusionStrategy like:
#RequiredArgsConstructor
public class IncludeListedFields implements ExclusionStrategy {
#NonNull
private Set<String> fieldsToInclude;
#Override
public boolean shouldSkipField(FieldAttributes f) {
return ! fieldsToInclude.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
then use it like:
Set<String> fieldsToInclude =
new HashSet<>(Arrays.asList("name", "genre", "secretID"));
ExclusionStrategy es = new IncludeListedFields(fieldsToInclude);
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls()
.addSerializationExclusionStrategy(es).create();
Note following things:
You should not now use the builder method .excludeFieldsWithoutExposeAnnotation.
By default Gson does not serialize fileds with null values so you need to use builder method .serializeNulls(). This does not generate Json with string values "" but just null.
In your example Json fields contained empty strings as values but you did not introduce default constructor Movie() that would initialize field values to empty strings so they remain null. But if you initialize them - say to empty string ""- then they are not null & you do not need to use builder method .serializeNulls().
BUT if you really need and want only to serialize based on #Expose(deserialize=true) then the ExclusionStrategy can be just:
public class PrintDeserializeTrue implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
Expose annotationExpose = f.getAnnotation(Expose.class);
if(null != annotationExpose) {
if(annotationExpose.deserialize())
return false;
}
return true;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}

Make GSON accept single objects where it expects arrays

I have bunch of model classes which have fields of type List<X> where X is one of many things (e.g. String, Integer, but also some of my own types). I'm using GSON to parse JSON representations of these models.
My problem is that the server I'm dealing with (which is beyond my control) somehow removed singleton arrays and replaces them by the contained object.
For example, instead of returning:
{
"foo": [ "bar"],
"bleh": [ { "some": "object" } ]
}
It returns:
{
"foo": "bar",
"bleh": { "some": "object" }
}
Now assume that the Java model class look something like this:
public class Model {
private List<String> foo;
private List<SomeObject> bleh;
}
Currently this causes GSON to throw an exception because it finds BEGIN_STRING or BEGIN_OBJECT where it expects BEGIN_ARRAY.
For arrays or lists of Strings this is easily solved using a TypeAdapter<List<String>>. But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter<List<?>>, because at some point you need to know the type.
So is there another way to configure GSON to be smart enough to turn single objects or values into arrays/lists? Or in other words, just "pretend" that the [ and ] are there where it expects to find them although they aren't there?
But the problem is I have Lists with many different element types and I don't want to write a separate TypeAdapter for each case. Nor have I been able to a generic TypeAdapter>, because at some point you need to know the type.
This is what type adapter factories are designed for: you can control every type in Gson instance configuration.
final class AlwaysListTypeAdapterFactory<E>
implements TypeAdapterFactory {
// Gson can instantiate it itself
private AlwaysListTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a List -- just delegate the job to Gson and let it pick the best type adapter itself
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Resolving the list parameter type
final Type elementType = resolveTypeArgument(typeToken.getType());
#SuppressWarnings("unchecked")
final TypeAdapter<E> elementTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(elementType));
// Note that the always-list type adapter is made null-safe, so we don't have to check nulls ourselves
#SuppressWarnings("unchecked")
final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(elementTypeAdapter).nullSafe();
return alwaysListTypeAdapter;
}
private static Type resolveTypeArgument(final Type type) {
// The given type is not parameterized?
if ( !(type instanceof ParameterizedType) ) {
// No, raw
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public void write(final JsonWriter out, final List<E> list) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
// This is where we detect the list "type"
final List<E> list = new ArrayList<>();
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
// If it's a regular list, just consume [, <all elements>, and ]
in.beginArray();
while ( in.hasNext() ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
break;
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
// An object or a primitive? Just add the current value to the result list
list.add(elementTypeAdapter.read(in));
break;
case NULL:
throw new AssertionError("Must never happen: check if the type adapter configured with .nullSafe()");
case NAME:
case END_ARRAY:
case END_OBJECT:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError("Must never happen: " + token);
}
return list;
}
}
}
Now you just have to tell Gson which fields are not well-formed.
Of course, you might configure the whole Gson instance to accept such lists, but let it be more precise using the #JsonAdapter annotation:
final class Model {
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<String> foo = null;
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<SomeObject> bleh = null;
#Override
public String toString() {
return "Model{" + "foo=" + foo + ", bleh=" + bleh + '}';
}
}
final class SomeObject {
final String some = null;
#Override
public String toString() {
return "SomeObject{" + "some='" + some + '\'' + '}';
}
}
Test data:
single.json
{
"foo": "bar",
"bleh": {"some": "object"}
}
list.json
{
"foo": ["bar"],
"bleh": [{"some": "object"}]
}
Example:
private static final Gson gson = new Gson();
public static void main(final String... args)
throws IOException {
for ( final String resource : ImmutableList.of("single.json", "list.json") ) {
try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43412261.class, resource) ) {
final Model model = gson.fromJson(jsonReader, Model.class);
System.out.println(model);
}
}
}
And the output:
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
Model{foo=[bar], bleh=[SomeObject{some='object'}]}
You can simply write your own JsonDeserializer where you check whether your bleh or foo are JsonObjects or JsonArrays.
To check if a JsonElement is an array or an object:
JsonElement element = ...;
if (element.isJsonObject()) {
//element is a JsonObject
} else if (element.isJsonArray()) {
//element is a JsonArray
}
One solution to this would be to write a custom TypeAdapterFactory which creates an adapter which peeks at the JSON data. If it encounters something other than a JSON array (or JSON null) it wraps it inside a JSON array before deserializing it:
// Only intended for usage with #JsonAdapter on fields
class SingleValueOrListAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Note: Cannot use getDelegateAdapter due to https://github.com/google/gson/issues/1028
TypeAdapter<T> listAdapterDelegate = gson.getAdapter(type);
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
listAdapterDelegate.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
JsonToken peeked = in.peek();
if (peeked == JsonToken.NULL || peeked == JsonToken.BEGIN_ARRAY) {
return listAdapterDelegate.read(in);
} else {
// Wrap JSON element in a new JSON array before deserializing it
JsonElement jsonElement = jsonElementAdapter.read(in);
JsonArray jsonArray = new JsonArray();
jsonArray.add(jsonElement);
return listAdapterDelegate.fromJsonTree(jsonArray);
}
}
};
}
}
The above implementation is designed only for usage with #JsonAdapter on fields, for example:
#JsonAdapter(SingleValueOrListAdapterFactory.class)
private List<MyClass> myField;
Compared to the currently accepted answer this provides the following advantages because it simply delegates the actual deserialization to listAdapterDelegate:
Custom List (or Collection) subclasses are supported because creation of them is delegated to Gson
Gson's default type resolution logic is used to determine the element type and to deserialize it
But it also has the following disadvantage:
Decreased performance because if the data is not already in a JSON array it is first deserialized to a JsonElement before the actual deserialization is performed
When using the GSON library, you could just check whether or not the following token is an object or an array. This of course requires you to go more fine grained while parsing the XML, but it allows you full control of what do you want to get from it. Sometimes we are not under control of the XML, and it could come handy.
This is an example to check if the next token is an object or an array, using the JsonReader class to parse the file:
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
jsonReader.beginArray()
} else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
jsonReader.beginObject()
}
And at the end of the array / object, you could do the same, but for the end tokens:
if (jsonReader.peek() == JsonToken.END_ARRAY) {
jsonReader.endArray()
} else if (jsonReader.peek() == JsonToken.END_OBJECT) {
jsonReader.endObject()
}
This way, you could have identical code (adding an extra check, to verify if you are on an array or on an object) to parse your array of objects, or a single object.
I had this same problem consuming xml / json from a vendor - they certainly weren't going to change their code for me :) There were several resources on the web that I used before changing adapting them to my own version This SO answer was very helpful. I spent some time looking at the gson code and finding a lot of private variable that I wanted access to. So, essentially what my custom collection adapter does is peek to see if the next element is an object. If not, we just delegate the read to the previous adapter (that we have overridden).
If the next element is an object, we use gson to process that. We then convert that to an array of one object. Use gson to write that to a string, then pass that string as a JsonReader to the underlying adapter. This can then create an instance of the underlying list and add the one element we have.
Here's the AdapterTypeFactory:
public enum ListSingleObjectAdapterFactory implements TypeAdapterFactory {
INSTANCE; // Josh Bloch's Enum singleton pattern
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Collection.class.isAssignableFrom(rawType)) {
return null;
}
TypeAdapter collectionAdapter = gson.getDelegateAdapter(this, typeToken);
Class genericClass = (Class) ((ParameterizedType) typeToken.getType())
.getActualTypeArguments()[0];
return new SingleObjectOrCollectionAdapter(
gson, collectionAdapter, genericClass);
}
}
Then the type adapter I have is:
public class SingleObjectOrCollectionAdapter<T> extends TypeAdapter<Collection<T>> {
private Class<T> adapterclass;
private Gson gson;
private TypeAdapter arrayTypeAdapter;
public SingleObjectOrCollectionAdapter(Gson gson, TypeAdapter<T> collectionTypeAdapter, Class<T> componentType) {
arrayTypeAdapter = collectionTypeAdapter;
this.gson = gson;
adapterclass = componentType;
}
#Override
public Collection<T> read(JsonReader reader) throws IOException {
Collection<T> collection;
JsonReader myReader = reader;
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
T inning = gson.fromJson(reader, adapterclass);
String s = gson.toJson(new Object[]{inning});
myReader = new JsonReader(new StringReader(s));
}
collection = (Collection)arrayTypeAdapter.read( myReader );
return collection;
}
#Override
public void write(JsonWriter writer, Collection<T> value) throws IOException {
arrayTypeAdapter.write(writer, value);
}
}
Finally, we need to register the adapter factory:
GsonBuilder gb = new GsonBuilder().registerTypeAdapterFactory(ListSingleObjectAdapterFactory.INSTANCE);
So far, it seems to be working well handling both single and multiple objects - although I wouldn't be surprised if it needs some tweaking down the road.

Gson Serialize Circular References Using Stubs

I'm trying to implement some simple Json serialization functionality but I'm having a hard time coping with the massive complexity of Gson.
So basically I have a bunch of Entity classes which reference each other with a lot of circular reference. To serialize this structure to JSON I want to keep track of the objects already serialized. The Entity classes all implement an interface called Identified which has one method String getId() giving a globally unique id. So during serializiation of one root element, I want to store all encountered ids in a Set and decide based on that set, whether to fully serialize an object or to serialize that object as a stub
"something": {
"__stub": "true",
"id": "..."
}
This shouldn't be too hard a task in my opinion, but I haven't been able to put something together. Using a custom JsonSerializer I'm not able to have an object (that is not to be serialized as a stub) serialized in the default way. Using a TypeAdapterFactory, I'm not able to access the actual object.
So, any help on how to achieve this, would be very nice!
Best regards
I'm not sure if it's possible easily. As far as I know, Gson promotes immutability and seems to lack custom serialization context support (at least I don't know if it's possible to use custom JsonSerializationContext wherever possible). Thus, one of possible work-around might be the following:
IIdentifiable.java
A simple interface to request a custom ID for an object.
interface IIdentifiable<ID> {
ID getId();
}
Entity.java
A simple entity that can hold another entity references in two manners:
a direct dependency to a "next" entity;
a collection of references to other references.
final class Entity
implements IIdentifiable<String> {
#SerializedName(ID_PROPERTY_NAME)
private final String id;
private final Collection<Entity> entities = new ArrayList<>();
private Entity next;
private Entity(final String id) {
this.id = id;
}
static Entity entity(final String id) {
return new Entity(id);
}
#Override
public String getId() {
return id;
}
Entity setAll(final Entity... entities) {
this.entities.clear();
this.entities.addAll(asList(entities));
return this;
}
Entity setNext(final Entity next) {
this.next = next;
return this;
}
}
IdentitySerializingTypeAdapterFactory.java
I didn't find any easier way rather than making it a type adapter factory, and, unfortunately, this implementation is totally stateful and cannot be reused.
final class IdentitySerializingTypeAdapterFactory
implements TypeAdapterFactory {
private final Collection<Object> traversedEntityIds = new HashSet<>();
private IdentitySerializingTypeAdapterFactory() {
}
static TypeAdapterFactory identitySerializingTypeAdapterFactory() {
return new IdentitySerializingTypeAdapterFactory();
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final boolean isIdentifiable = IIdentifiable.class.isAssignableFrom(typeToken.getRawType());
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
if ( isIdentifiable ) {
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
final IIdentifiable<?> identifiable = (IIdentifiable<?>) value;
final Object id = identifiable.getId();
if ( !traversedEntityIds.contains(id) ) {
delegateAdapter.write(out, value);
traversedEntityIds.add(id);
} else {
out.beginObject();
out.name(REF_ID_PROPERTY_NAME);
writeSimpleValue(out, id);
out.endObject();
}
}
#Override
public T read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
}
return delegateAdapter;
}
}
The type adapter firstly tries to check if a given entity has been already traversed. If yes, then it's writing a special object similar to your one (the behavior could be rewritten via the strategy pattern, of course, but let it be more simple). If no, then the default type adapter is obtained, and then the given entity is delegated to that adapter, and registered as a traversed one if the latter type adapter succeeds.
The rest
And here is the rest.
SystemNames.java
final class SystemNames {
private SystemNames() {
}
private static final String SYSTEM_PREFIX = "__$";
static final String ID_PROPERTY_NAME = SYSTEM_PREFIX + "id";
static final String REF_ID_PROPERTY_NAME = SYSTEM_PREFIX + "refId";
}
GsonJsonWriters.java
final class GsonJsonWriters {
private GsonJsonWriters() {
}
static void writeSimpleValue(final JsonWriter writer, final Object value)
throws IOException {
if ( value == null ) {
writer.nullValue();
} else if ( value instanceof Double ) {
writer.value((double) value);
} else if ( value instanceof Long ) {
writer.value((long) value);
} else if ( value instanceof String ) {
writer.value((String) value);
} else if ( value instanceof Boolean ) {
writer.value((Boolean) value);
} else if ( value instanceof Number ) {
writer.value((Number) value);
} else {
throw new IllegalArgumentException("Cannot handle values of type " + value);
}
}
}
Testing
In the test below, there are three entities identified by FOO, BAR, and BAZ string identifiers. All of them have circular dependencies like this:
FOO -> BAR, BAR -> BAZ, BAZ -> FOO using the next property;
FOO -> [BAR, BAZ], BAR -> [FOO, BAZ], BAZ -> [FOO, BAR] using the entities property.
Since the type adapter factory is stateful, even GsonBuilder must be created from scratch thus not having "spoiled" state between use. Simply speaking, once a Gson instance is used once, it must be disposed, so there are GsonBuilder suppliers in the test below.
public final class Q41213747Test {
private static final Entity foo = entity("FOO");
private static final Entity bar = entity("BAR");
private static final Entity baz = entity("BAZ");
static {
foo.setAll(bar, baz).setNext(bar);
bar.setAll(foo, baz).setNext(baz);
baz.setAll(foo, bar).setNext(foo);
}
#Test
public void testSerializeSameJson() {
final String json1 = newSerializingGson().toJson(foo);
final String json2 = newSerializingGson().toJson(foo);
assertThat("Must be the same between the calls because the GSON instances are stateful", json1, is(json2));
}
#Test
public void testSerializeNotSameJson() {
final Gson gson = newSerializingGson();
final String json1 = gson.toJson(foo);
final String json2 = gson.toJson(foo);
assertThat("Must not be the same between the calls because the GSON instance is stateful", json1, is(not(json2)));
}
#Test
public void testOutput() {
out.println(newSerializingGson().toJson(foo));
}
private static Gson newSerializingGson() {
return newSerializingGson(GsonBuilder::new);
}
private static Gson newSerializingGson(final Supplier<GsonBuilder> defaultGsonBuilderSupplier) {
return defaultGsonBuilderSupplier.get()
.registerTypeAdapterFactory(identitySerializingTypeAdapterFactory())
.create();
}
}
{
"__$id": "FOO",
"entities": [
{
"__$id": "BAR",
"entities": [
{
"__$refId": "FOO"
},
{
"__$id": "BAZ",
"entities": [
{
"__$refId": "FOO"
},
{
"__$refId": "BAR"
}
],
"next": {
"__$refId": "FOO"
}
}
],
"next": {
"__$refId": "BAZ"
}
},
{
"__$refId": "BAZ"
}
],
"next": {
"__$refId": "BAR"
}
}
Deserialization of such stuff looks really complicated. At least using GSON facilities.
Do you consider rethinking your JSON model in order to avoid circular dependencies in JSON output? Maybe decomposing your objects to a single map like Map<ID, Object> and making references transient or #Expose-annotated could be easier for you to use? It would simplify deserialization as well.

Gson deserialize polymorphic member List variable is null

Following is the class diagram of the problem domain. We have JSON decoded messages with different semantics which trigger different methods in the code (intialize, update) for different views.
The Message is deserialized fine into either InitMessage or DataMessage using the solution proposed here using a RuntimeTypeAdapterFactory and registering all possible subtypes. However, the DataMessage.value list is empty (not deserialized). The problem is the nested polymorphic member in DataMessage.
The adapter factories:
RuntimeTypeAdapterFactory<Message> messageAdapterFactory = RuntimeTypeAdapterFactory
.of(Message.class, "MESSAGE_TYPE")
.registerSubtype(InitializationMessage.class, "INIT")
.registerSubtype(DataMessage.class, "DATA");
RuntimeTypeAdapterFactory<DataValue> dataAdapterFactory = RuntimeTypeAdapterFactory
.of(DataValue.class, "NAME")
.registerSubtype(DataValueA.class, "A")
.registerSubtype(DataValueB.class, "B")
.registerSubtype(DataValueC.class, "C");
The creation of the message:
TypeToken<Message> typeToken = new TypeToken<Message>() {};
Message msg = gson.fromJson(json, typeToken.getType());
DataMessage class:
public class DataMessage extends Message {
private List<DataValue> value;
public List<DataValue> getValue() {
return value;
}
public void setValue(List<DataValue> value) {
this.value= value;
}
}
DataValueA class:
public class DataValueA extends DataValue {
private Map<String, Float> value;
public float getValue(String location) {
return value.get(location);
}
}
The corresponding JSON:
{
"MESSAGE_TYPE" : "DATA",
"VALUE" : [
{
"NAME" : "C",
"VALUE" : 1.3
},
{
"NAME" : "A",
"VALUE" : {
"FL" : 18.4,
"FR" : 18.4,
"RL" : 18.4,
"RR" : 18.4
}
}]
}
I want the DataValue be deserialized into their respective subclass (DataValueA ...).
A solution is to use the GsonBuilder.registerTypeAdapter method to register custom JsonDeserializer. The way is to use a field in the message to define which subclass will be created (just like with RuntimeTypeAdapterFactory which is not shipped by default and lives in gson-extra).
The deserializer will be registered for each abstract superclass.
gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Message.class, new MessageAdapter())
.registerTypeAdapter(DataValue.class, new DataValueAdapter())
.create();
Given the field to distinguish subtypes is named NAME you can define the deserialize function as following. There is a mapping from the content of the field to the respective subclass.
public class DataValueAdapter implements JsonDeserializer<DataValue> {
private final static Map<String, Class<?>> FieldToClass;
static {
FieldToClass = new HashMap<>();
FieldToClass.put("PERFORMANCE", PerformanceDataValue.class);
FieldToClass.put("TIRE_SLIP", TireSlipDataValue.class);
}
#Override
public DataValue deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String dataType = jsonObject.get("NAME").getAsString();
return context.deserialize(json, FieldToClass.get(dataType));
}
}
In order to make the reflective deserializer (which will be used for the subclasses as long as you are okay with the standard deserializer) work the subclasses need to state #SerializedName at the properties. Without it did not work for me.

jackson delay deserializing field

I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.

Categories

Resources