java - GSON:在两个类中引用相同的对象,解码后实例重复

标签 java android json gson

我正在使用 GSON 将数据保存并恢复到我的应用程序中。问题是,在某些情况下,我有两个不同对象引用的对象,我的意思是,引用了同一个实例。所以多条路径可以通向同一个对象。

当持久化我的模型的解码 GSON 字符串时,如果在两个地方存在同一个对象的两个引用,它持久化它们显然是正确的,但是当再次打开应用程序并加载数据和解码 GSON 字符串时,两个正在创建同一对象的不同实例,而不是同一实例。在第一个实例中进行更改不会反射(reflect)在第二个实例中,因为在解码 json 后是不同的对象。

这是问题的踪迹:

有模型、人和车:

public class Model{
    Car car;
    Person person;
}

public class Person{
    Car car;
}

我将相同的汽车实例设置为模型和人:

Car car = new Car();
model.setCar(car);
person.setCar(car);

car 是 car 和 person 中的同一个实例,现在我用 GSON 对数据进行编码和持久化:

Gson gson = new Gson();
String json = gson.toJson(model);

然后我关闭应用程序并重新打开应用程序,然后解码 json 字符串以恢复模型:

Gson gson = new Gson();
gson.fromJson(json, Model.class);

现在,我有两个不同的 Car 实例,一个在 person 内部,另一个在 Model 内部,它们不是同一个实例,但必须是同一个实例!如果我修改了 Model 的汽车,那么 person 的汽车没有被修改,这是一个错误。

如何解决?

最佳答案

Gson 默认情况下不提供任何方式来缓存实例并检查它是否已经被看到。为此,我们需要实现自定义 com.google.gson.TypeAdapterFactory。此外,我们需要假设 Car 类(如果需要,还有 Person 类)正确实现了 public boolean equals(Object o) public int hashCode() 所以我们可以使用 Map 来缓存所有实例。

假设您的模型如下所示:

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);
    }
}

CarPerson 类有我们用来区分实例的 id 字段。您可以在实现中使用任何您想要的属性。

使用 Map 缓存实例的自定义适配器实现:

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);
            }
        };
    }
}

下面是如何使用它的示例:

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();
    }
}

上面的代码打印,首先是JSON:

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

然后:

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

注意事项

CachedInstancesTypeAdapterFactory 实现不是线程安全的。此外,当您想要使用 Car 反序列化 JSON 负载时,您必须始终为每个线程和每次尝试创建新的 Gson 对象Person 实例。原因是 CachedInstancesTypeAdapterFactory#cachedMaps 对象只能使用一次。

另见:

关于java - GSON:在两个类中引用相同的对象,解码后实例重复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58841054/

相关文章:

android - 带有始终可见的选项卡图像的抽屉导航

java - 从 Java keystore 获取证书

java - 从 Activity 外部调用 startActivity()

java - 为什么我的curl命令不能与Java ProcessBuilder API一起使用

java - Grails 中的 servletContext 在哪里可用?

android - 钛 sdk "ERROR: Asset package include '/root/.titanium/mobilesdk/linux/3.3.0.GA/android/titanium.jar' 未找到“

android - 如何从库/依赖项中获取客户端应用程序的应用程序版本?

json - VSCode 片段,将由下划线分隔的小写字符串转换为 CamelCase?

json - 记录 JSON 文件结构的最佳实践?

javascript - jQuery 多层 JSON