Gson: Custom deserialization if certain field is present

1.9k views Asked by At

I have a class that looks as follows

class Person {
    Long id;
    String firstName;
    int age;
}

and my input either looks like this:

{ "id": null, "firstName": "John", "age": 10 }

or like this:

{ "id": 123 }

The first variant represents a "new" (non-persisted) person and the second refers to a person by its database id.

If id is non-null, I would like to load the object from database during deserialization, otherwise fallback on regular parsing and deserialize it as a new object.

What I've tried: I currently have a JsonDeserializer for database-deserialization, but as I understand it, there is no way to "fall back" on regular parsing. According to this answer I should use a TypeAdapterFactory and the getDelegateAdapter. My problem with this approach is that I'm given a JsonReader (and not for instance a JsonElement) so I can't determine if the input contains a valid id without consuming input.

Any suggestions on how to solve this?

2

There are 2 answers

0
aioobe On BEST ANSWER

I think I managed to figure this out with the help of the answer over here.

Here is a working type adapter factory:

new TypeAdapterFactory() {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {

            final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
            final TypeAdapter<JsonElement> elementAdapter =
                    gson.getAdapter(JsonElement.class);

            // Are we asked to parse a person?
            if (!type.getType().equals(Person.class))
                return null;

            return new TypeAdapter<T>() {

                @Override
                public T read(JsonReader reader) throws IOException {

                    JsonElement tree = elementAdapter.read(reader);
                    JsonElement id = tree.getAsJsonObject().get("id");

                    if (id == null)
                        return delegate.fromJsonTree(tree);

                    return (T) findObj(id.getAsLong());
                }

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

I haven't fully tested it yet and I'll get back and revise it if needed. (Posting it now to open up for feed back on the approach.)

3
MikO On

I don't know if I understand your question correctly, but if you already have a JsonDeserializer, you should have a method like this one in there:

public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { ... }

In this method you have the object context of type JsonDeserializationContext, which allows you to invoke default deserialization on a specified object.

So, you could do something like inside your custom deserializer:

//If id is null...
Person person = context.deserialize(json, Person.class);

See JsonDeserializationContext documentation.