Skip to content Skip to sidebar Skip to footer

Gson: Same Object Referenced In Two Classes, Duplicated Instance After Decode

I'm using GSON for persist and recover data into my app. The problem is that, in come cases, I have objects that are referenced in two different objects, I mean, the same instance

Solution 1:

Gson by default does not provide any way to cache instances and check whether it was already seen or not. To do that we need to implement custom com.google.gson.TypeAdapterFactory. Also, we need to assume that Car class (and Person class if needed) implements properly public boolean equals(Object o) and public int hashCode() so we can use Map to cache all instances.

Let's assume that your model looks like below:

classModel {
    privateCar car;
    privatePerson person;

    // getters, setters, toString
}

classPerson {
    private int id;
    privateString name;
    privateCar car;

    // getters, setters, toString@Overridepublicbooleanequals(Object o) {
        if (this == o) returntrue;
        if (o == null || getClass() != o.getClass()) returnfalse;
        Person person = (Person) o;
        return id == person.id;
    }

    @Overridepublic int hashCode() {
        returnObjects.hash(id);
    }
}

classCar {
    private int id;
    privateString name;

    // getters, setters, toString@Overridepublicbooleanequals(Object o) {
        if (this == o) returntrue;
        if (o == null || getClass() != o.getClass()) returnfalse;
        Car car = (Car) o;
        return id == car.id;
    }

    @Overridepublic int hashCode() {
        returnObjects.hash(id);
    }
}

Car and Person classes have id fields which we use to distinguish instances. You can use any properties you want in your implementation.

Custom adapter implementation which uses Map to cache instances:

classCachedInstancesTypeAdapterFactoryimplementsTypeAdapterFactory {

    privatefinal Map<Class, Map> cachedMaps = newHashMap<>();

    publicCachedInstancesTypeAdapterFactory(Set<Class> customizedClasses) {
        Objects.requireNonNull(customizedClasses);
        customizedClasses.forEach(clazz -> cachedMaps.compute(clazz, (c, m) -> newHashMap<>()));
    }

    publicfinal <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (cachedMaps.containsKey(type.getRawType())) {
            final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
            return createCustomTypeAdapter(delegate);
        }

        returnnull;
    }

    @SuppressWarnings("unchecked")private <T> TypeAdapter<T> createCustomTypeAdapter(TypeAdapter<T> delegate) {
        returnnewTypeAdapter<T>() {
            @Overridepublicvoidwrite(JsonWriter out, T value)throws IOException {
                delegate.write(out, value);
            }

            @Overridepublic T read(JsonReader in)throws IOException {
                Objectdeserialized= delegate.read(in);

                MaptInstances= Objects.requireNonNull(cachedMaps.get(deserialized.getClass()));
                return (T) tInstances.computeIfAbsent(deserialized, k -> deserialized);
            }
        };
    }
}

And below you find example how to use it:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

publicclassGsonApp {

    publicstaticvoidmain(String[] args) {
        Gsongson= createGson();

        Stringjson= gson.toJson(createModel());
        System.out.println(json);

        Modelresult= gson.fromJson(json, Model.class);

        System.out.println(result);
        System.out.println("Two car instances are the same: " + (result.getCar() == result.getPerson().getCar()));
    }

    privatestatic Model createModel() {
        Carcar=newCar();
        car.setId(9943);
        car.setName("Honda");

        Personperson=newPerson();
        person.setId(123);
        person.setName("Jon");
        person.setCar(car);

        Modelmodel=newModel();
        model.setCar(car);
        model.setPerson(person);
        return model;
    }

    privatestatic Gson createGson() {
        Set<Class> classes = newHashSet<>();
        classes.add(Car.class);
        classes.add(Person.class);

        returnnewGsonBuilder()
                .setPrettyPrinting()
                .registerTypeAdapterFactory(newCachedInstancesTypeAdapterFactory(classes))
                .create();
    }
}

Above code prints, firstly JSON:

{"car":{"id":9943,"name":"Honda"},"person":{"id":123,"name":"Jon","car":{"id":9943,"name":"Honda"}}}

And after that:

Model{car=Car{id=9943, name='Honda'}, person=Person{id=123, name='Jon', car=Car{id=9943, name='Honda'}}}
Two car instances are the same: true

Note

Above CachedInstancesTypeAdapterFactory implementation is not thread safety. Moreover, you must create always new Gson object for each thread and for each attempt when you want to deserialise JSON payload with Car and Person instances. Reason is CachedInstancesTypeAdapterFactory#cachedMaps object can be used only once.

See also:

Post a Comment for "Gson: Same Object Referenced In Two Classes, Duplicated Instance After Decode"