0

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:

autos

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??

sh7411usa
  • 196
  • 1
  • 13
  • 1
    This is a complex structure. How about a 2 step deserialization? first step will be to use map with . after that deserialize the value String again. – Fahim Fahad Jan 26 '22 at 16:17
  • can you demonstrate how to do that in one or two easy steps with gson? – sh7411usa Jan 26 '22 at 16:51
  • Something like this: 1st step: public class responseObject { String field_1; String field_2; Field3 field_3; String field_4; class Field3{ Map field_3; } class ArrayObject{ String array_field_1; String array_field_2; String array_field_3; } } 2nd step: for(String key: field_3.keySet()) { //apply deserialization for field_3.get(key) } – Fahim Fahad Jan 27 '22 at 06:51

2 Answers2

0

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.

Marcono1234
  • 5,856
  • 1
  • 25
  • 43
0

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();
}
bhspencer
  • 13,086
  • 5
  • 35
  • 44
  • working! thank you. accepted this answer because it avoids having to write a custom `TypeAdapter` which is more in the spirit of the simplicity of `GSON`s easy deserializing... – sh7411usa Mar 16 '22 at 11:53