0

I have been looking around but could not find any conclusion for my problem, I'll try to describe it the best way I can.

I'm calling an API that can respond 2 ways (note that class names are only examples, not the real names):

Bad response:

{
        "CustomObject_1" : {
            "CustomObject_1.1" : {
                ...
            },
            "CustomObject_1.2" : [
                [ ]
            ]
        },
        "CustomObject_2" : {
            ...
        }

Good response:

{
        "CustomObject_1" : {
            "CustomObject_1.1" : {
                ...
            },
            "CustomObject_1.2" : [
                "CustomObject_1.2.1" : {
                ...
                }
            ]
        },
        "CustomObject_2" : {
            ...
        }

The difference is in CustomObject_1.2 that can be a custom object (that contains more layers of custom objects inside), or it can be an empty array.

I have a deserializer that corrects the bad response, but it makes the good response null, instead of using the default serialization.

class CustomObject_1.2Deserializer

public CustomObject_1.2 deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonArray()) {
            JsonArray jsonArray = json.getAsJsonArray();
            if (jsonArray.size() == 0) {
                System.out.println(jsonArray);  // return an empty Totals object
                return new CustomObject_1.2();
            }
        } else if (json.isJsonObject()) {
            JsonObject jsonObject = json.getAsJsonObject();
            if (jsonObject.has("CustomObject_1.2.1") && jsonObject.get("CustomObject_1.2.1").isJsonArray()) {
                JsonArray competitorsArray = jsonObject.getAsJsonArray("competitors");
                System.out.println(competitorsArray);   //valid object so use default serialization
            }
        }
        return null;
    }

Is there a way to ignore the deserialization when the json comes as a valid object?

In case it's needed here is the function that handles the gson serialization:

private MyDtoInterface processResponse(String message) {
        MyDtoInterface mDto = null;
        try {
                Gson gson = new GsonBuilder()
                        .registerTypeAdapter(CustomObject_1.2.class, new CustomObject_1.2Deserializer())
                        .create();
                mDto = gson.fromJson(message, CustomparentDTO.class);
        } catch (Exception e) {
            System.out.println("Json format not compatible with ParentDTO");
        }
        return mDto;
    }

Thank you in advance!

I've lost count of what I've tried. And most examples I can find use primitives and not CustomObjects inside CustomObjects.

I was trying to only deserialize the invalid json. If I try to build a new CustomObject_1.2() it will be endless because it's attributes are not primites like String, Integer, Date, ... they are custom objecst inside objects.

The code above will return the following: Bad response (will build a good DTO):

{
        "CustomObject_1" : {
            "CustomObject_1.1" : {
                ...
            },
            "CustomObject_1.2" : [
                "CustomObject_1.2.1" : null
            ]
        },
        "CustomObject_2" : {
            ...
        }

Good response (will build a null DTO):

{
        "CustomObject_1" : {
            "CustomObject_1.1" : {
                ...
            },
            "CustomObject_1.2" : [
                All elements are null
            ]
        },
        "CustomObject_2" : {
            ...
        }
Marta
  • 1
  • 2
  • Did you edit your code before pasting it here? Because as far as I know `CustomObject_1.2` is not a valid Java class name. In general please choose valid class names in your examples (e.g. `CustomObject_1_2`) and make sure they actually compile, or even better that they are [minimal and reproducible](https://stackoverflow.com/help/minimal-reproducible-example). – Marcono1234 Jul 12 '23 at 21:40
  • Also it looks like the syntax of the JSON snippets you provided is invalid; `[ ... "CustomObject_1.2.1": ... ]` is not valid. Did you mean `{ "CustomObject_1.2.1": ... }`? Please verify that they are correct. Please also provide the code for your `CustomObject_1` class so that the complete structure of your data is clearer. – Marcono1234 Jul 12 '23 at 21:54

2 Answers2

0

I've changed my deserializer code like below and it looks like it's working like it should, although I'm not sure if it makes sense.

public CustomObject_1.2 deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
    if (json.isJsonArray()) {
        JsonArray jsonArray = json.getAsJsonArray();
        if (jsonArray.size() == 0) {
            System.out.println(jsonArray);  // return an empty Totals object
            return new CustomObject_1.2();
        }
    } else if (json.isJsonObject()) {
        JsonObject jsonObject = json.getAsJsonObject();
        if (jsonObject.has("CustomObject_1.2.1") && jsonObject.get("CustomObject_1.2.1").isJsonArray()) {
            JsonArray competitorsArray = jsonObject.getAsJsonArray("competitors");
            System.out.println(competitorsArray);   //valid object so use default serialization
            return new Gson.fromJson(jsonObject, CustomObject_1.2.class);
        }
    }
    return null;
}
Marta
  • 1
  • 2
0

Your question is a bit similar to this question where either a single object or an array of objects should be deserialized. The solution to this is therefore also similar:

You can create a custom TypeAdapterFactory whose created adapter peeks at the JSON data. If it finds a JSON array, it expects that the array is empty and returns new CustomObject_1_2(); otherwise it delegates deserialization to the actual adapter (if you haven't specified one, Gson defaults to using reflection).

The advantage compared to your code is that it uses TypeAdapterFactory and TypeAdapter rather than JsonDeserializer and is therefore likely more efficient because it directly operates on the JSON data stream, and also it reuses the same Gson instance and therefore also considers any custom adapters which you might registered for the types of the fields of CustomObject_1_2.

For example:

class CustomObject_1_2_AdapterFactory implements TypeAdapterFactory {
    public static final CustomObject_1_2_AdapterFactory INSTANCE = new CustomObject_1_2_AdapterFactory();

    private CustomObject_1_2_AdapterFactory() { }

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // Only handle CustomObject_1_2, let other factories handle all other types
        if (type.getRawType() != CustomObject_1_2.class) {
            return null;
        }

        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<T>() {
            @Override
            public T read(JsonReader in) throws IOException {
                if (in.peek() == JsonToken.BEGIN_ARRAY) {
                    // Expecting an empty JSON array
                    in.beginArray();
                    in.endArray();

                    // Cast is safe because factory checked that type is CustomObject_1_2
                    @SuppressWarnings("unchecked")
                    T t = (T) new CustomObject_1_2();
                    return t;
                } else {
                    return delegate.read(in);
                }
            }

            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }
        };
    }
}

And then register it like this:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(CustomObject_1_2_AdapterFactory.INSTANCE)
    .create();

If you don't just have to differentiate between whether the data is a JSON array or a JSON object and need additional checks such as for the CustomObject_1.2.1 property as shown in your code, then it would be more complicated because you would first have to parse the JSON data as JsonObject to peek at the fields.

Marcono1234
  • 5,856
  • 1
  • 25
  • 43