使用gson进行深层复制,但保留相同的对象标识

w8biq8rn  于 2022-11-06  发布在  其他
关注(0)|答案(1)|浏览(288)

我目前正在通过使用gson序列化/反序列化对象来深度克隆一个嵌套对象。这很好--克隆对象中的更改不会反映在原始对象中。
如果在原始对象中具有相同对象标识的嵌套对象在克隆对象中也具有相同的(新的)对象标识,这将是很好的。
下面是一个示例:

public class Person {
    private String firstName;
    private String LastName;

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return LastName;
    }
    public void setLastName(String lastName) {
        LastName = lastName;
    }
    public String toString() {
        return this.firstName + " " + this.LastName;
    }
}

public class PersonManager {
    Person person1;
    Person person2;
    public Person getPerson1() {
        return person1;
    }
    public void setPerson1(Person person1) {
        this.person1 = person1;
    }
    public Person getPerson2() {
        return person2;
    }
    public void setPerson2(Person person2) {
        this.person2 = person2;
    }
}

public class Test {

    public static void main(String[] args) {
        Person person = new Person();
        person.setFirstName("Hans");
        person.setLastName("Moleman");

        PersonManager personManager = new PersonManager();
        personManager.setPerson1(person);
        personManager.setPerson2(person);

        //Both are the same
        System.out.println(personManager.getPerson1());
        System.out.println(personManager.getPerson2());

        //Changing person1 also changes person2
        personManager.getPerson1().setFirstName("Homer");
        personManager.getPerson1().setLastName("Simpson");
        System.out.println(personManager.getPerson1());
        System.out.println(personManager.getPerson2());

        //Serialize and deserialize with gson
        Gson gson = new GsonBuilder().create();
        String json = gson.toJson(personManager, PersonManager.class);
        PersonManager copyOfPersonManager = (PersonManager) gson.fromJson(json, PersonManager.class);

        //Reference information gets lost - only person1 changes
        copyOfPersonManager.getPerson1().setFirstName("Ned");
        copyOfPersonManager.getPerson1().setLastName("Flanders");
        System.out.println(copyOfPersonManager.getPerson1());
        System.out.println(copyOfPersonManager.getPerson2());
    }

}

gson是否有任何可能性/扩展来跟踪相同的对象标识?我不关心json看起来如何,因为我只对克隆感兴趣。

odopli94

odopli941#

我实现了一个自定义的TypeAdapter(Factory),它可以拦截JSON的阅读。在编写JSON时,我将对象的(原始)哈希代码存储为一个附加的JSON元素。对于具有相同引用的对象,哈希代码应该相同。
当再次阅读JSON时,我用hash代码作为键,用创建的java对象作为值填充一个map。当json解析器到达一个具有hash代码的元素时,该元素已经存在于map中,它不会创建一个新的java对象,而是重用map中的那个对象。
这样,所有对象仍具有新的对象标识/引用,但保留了相同的对象标识。
下面是TypeAdapterFactory的实现:

public class ReferenceGuardTypeAdapterFactory implements TypeAdapterFactory {
    private Map<Integer, Object> originalRefIdToNewObjectMap = new HashMap<>();

    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);
        return new TypeAdapter<T>() {
            public void write(JsonWriter out, T value) throws IOException {
                int refId = System.identityHashCode(value);
                JsonElement tree = delegate.toJsonTree(value);
                if(tree.isJsonObject()) {
                    JsonObject custom = tree.getAsJsonObject();
                    custom.add("origRefId", new JsonPrimitive(refId));
                    elementAdapter.write(out, tree);
                } else {
                    delegate.write(out, value);
                }
            }

            public T read(JsonReader in) throws IOException {
                JsonElement tree = elementAdapter.read(in);
                if(tree.isJsonObject()) {
                    JsonObject custom = tree.getAsJsonObject();
                    JsonElement origRefId = custom.remove("origRefId");
                    Object alreadyCreatedObject = ReferenceGuardTypeAdapterFactory.this.originalRefIdToNewObjectMap.get(origRefId.getAsInt());
                    if(alreadyCreatedObject == null) {
                        alreadyCreatedObject = delegate.fromJsonTree(tree);
                        ReferenceGuardTypeAdapterFactory.this.originalRefIdToNewObjectMap.put(origRefId.getAsInt(), alreadyCreatedObject);
                    }
                    return (T) alreadyCreatedObject;
                }
                return delegate.fromJsonTree(tree);
            }
        };
    }
}

在创建Gson对象时必须设置TypeAdapterFactory:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ReferenceGuardTypeAdapterFactory()).create();

相关问题