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:
class Model {
private Car car;
private Person person;
// getters, setters, toString
}
class Person {
private int id;
private String name;
private Car car;
// getters, setters, toString
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
class Car {
private int id;
private String name;
// getters, setters, toString
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return id == car.id;
}
@Override
public int hashCode() {
return Objects.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:
class CachedInstancesTypeAdapterFactory implements TypeAdapterFactory {
private final Map<Class, Map> cachedMaps = new HashMap<>();
public CachedInstancesTypeAdapterFactory(Set<Class> customizedClasses) {
Objects.requireNonNull(customizedClasses);
customizedClasses.forEach(clazz -> cachedMaps.compute(clazz, (c, m) -> new HashMap<>()));
}
public final <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);
}
return null;
}
@SuppressWarnings("unchecked")
private <T> TypeAdapter<T> createCustomTypeAdapter(TypeAdapter<T> delegate) {
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
Object deserialized = delegate.read(in);
Map tInstances = 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;
public class GsonApp {
public static void main(String[] args) {
Gson gson = createGson();
String json = gson.toJson(createModel());
System.out.println(json);
Model result = gson.fromJson(json, Model.class);
System.out.println(result);
System.out.println("Two car instances are the same: " + (result.getCar() == result.getPerson().getCar()));
}
private static Model createModel() {
Car car = new Car();
car.setId(9943);
car.setName("Honda");
Person person = new Person();
person.setId(123);
person.setName("Jon");
person.setCar(car);
Model model = new Model();
model.setCar(car);
model.setPerson(person);
return model;
}
private static Gson createGson() {
Set<Class> classes = new HashSet<>();
classes.add(Car.class);
classes.add(Person.class);
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapterFactory(new CachedInstancesTypeAdapterFactory(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: