Gson: Same Object Referenced In Two Classes, Duplicated Instance After Decode
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"