sub123
sub123

Reputation: 93

Gson - deserialising different types

I am processing incoming JSON Strings and want to deserialise them into typed POJO objects using GSON. However the server pushing the strings can send different types of object - though the type is defined in the JSON payload.

So looking at the below two JSON strings where I have a tradeEvent and an errorEvent object (have 5 other types like settlementEvent, paymentEvent etc).

How could I deserialise this to the actual POJO in GSON (presumably using generics) since I don't know the type until runtime - as you can see the second level element contains the actual object type (tradeEvent, errorEvent etc).

Should also add - in terms of the POJO, would I represent the second element (ie tradeEvent, errorEvent) as an object, or String?

 {
  "data": {
    "tradeEvent": {
      "tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015",
      "status": 2,
      "eventDescription": "Trade Settled"
    }
  }
}


{
  "data": {
    "errorEvent": {
      "Uuid": "3a36ae26-ba41-40d5-b11d-d8d842eb2356",
    "failureCode": 2, "tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015", "errorMessage": "Returned error: Exception while processing transaction: trade not matched"
    }
  }
}

Thanks for any guidance.

Upvotes: 0

Views: 56

Answers (1)

Implementing a data wrapper class to extract an event object would probably be the most simple way:

final class WrapperDto {

    @Nullable
    @SerializedName("data")
    @Expose(serialize = false, deserialize = true)
    private final DataDto data;

    @SuppressWarnings("unused")
    private WrapperDto(@Nullable final DataDto data) {
        this.data = data;
    }

    @Nullable
    <E extends Event> E toEvent() {
        if ( data == null ) {
            return null;
        }
        return data.toEvent();
    }

    private static final class DataDto {

        @Nullable
        @SerializedName("tradeEvent")
        @Expose(serialize = false, deserialize = true)
        private final Event.Trade tradeEvent;

        @Nullable
        @SerializedName("errorEvent")
        @Expose(serialize = false, deserialize = true)
        private final Event.Error errorEvent;

        @SuppressWarnings("unused")
        private DataDto(@Nullable final Event.Trade tradeEvent, @Nullable final Event.Error errorEvent) {
            this.tradeEvent = tradeEvent;
            this.errorEvent = errorEvent;
        }

        @Nullable
        private <E extends Event> E toEvent()
                throws IllegalStateException {
            @Nullable
            Event bestEvent = null;
            for ( final Event event : new Event[]{ tradeEvent, errorEvent } ) {
                if ( bestEvent == null ) {
                    bestEvent = event;
                } else if ( event != null ) {
                    throw new IllegalStateException("Ambiguity detected. event=" + event.getClass().getSimpleName() + ", bestEvent=" + bestEvent.getClass().getSimpleName());
                }
            }
            @SuppressWarnings("unchecked")
            final E castBestEvent = (E) bestEvent;
            return castBestEvent;
        }

    }

}

This approach, I believe, is much easier to implement than adapting RuntimeTypeAdapterFactory to your needs. However, implementing a custom type adapter might detect ambiguous fields right on reading therefore not deserializing each field (that costs some more heap).

The approach above would pass the following test:

private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .excludeFieldsWithoutExposeAnnotation()
        .create();
...
try ( final JsonReader jsonReader = open("tradeEvent.json") ) {
    Assertions.assertTrue(gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Trade);
}
try ( final JsonReader jsonReader = open("errorEvent.json") ) {
    Assertions.assertTrue(gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Error);
}
Assertions.assertThrows(IllegalStateException.class, () -> {
    try ( final JsonReader jsonReader = open("tradeAndErrorEvent.json") ) {
        gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent();
    }
});

Upvotes: 1

Related Questions