Gson JsonObject复制值影响其他JsonObject实例

6

我在使用gson(我的gson版本是2.3.1)时遇到了一个奇怪的问题。

我有一个叫做jsonObject的JsonObject实例(JsonObject jsonObject)。

jsonObject有值,不为空。

然后我创建了另一个JsonObject实例,JsonObject tempOject = jsonObject;。

因此,当我尝试在tempObject中删除元素时,比如说tempObject.remove("children");

那么这段代码会影响到jsonObject实例。

以下是代码片段:

jsonObject              = element.getAsJsonObject();
        JsonElement tempElement = element;
        JsonObject tempObject   = jsonObject;
        String tempJson;

        if(tempObject.has("children")){
            tempObject.remove("children");
            tempJson = tempObject.toString();
            tempElement = new JsonParser().parse(tempJson);
        }

        if(nodes.isEmpty()){
            elements = new ArrayList<>();
            nodes.put(iterator, elements);
        }
        if(!nodes.containsKey(iterator)){
            elements = new ArrayList<>();
            nodes.put(iterator, elements);
        }
        nodes.get(iterator).add(tempElement);

        if (jsonObject.has("children")){
            tempNextJson        = jsonObject.get("children").toString();
            tempCurrJson        = jsonObject.toString();

            tempIterator++;
            metaDataProcessor(tempNextJson, tempCurrJson, tempNextJson, tempIterator, maxLevel);
        }

我已经阅读了gson JsonObject类,它使用深度复制方法。这不应该影响引用,因为JsonObject使用深度值复制,所以返回的JsonObject对象是新的。
但是为什么会发生这种情况呢?
无论如何,在JsonObject类中有deepCopy方法。
JsonObject deepCopy() {
    JsonObject result = new JsonObject();
    Iterator i$ = this.members.entrySet().iterator();

    while(i$.hasNext()) {
        Entry entry = (Entry)i$.next();
        result.add((String)entry.getKey(), ((JsonElement)entry.getValue()).deepCopy());
    }

    return result;
}

但是这是JsonElement类中的一个抽象方法,由JsonObject实现。由于该属性未设置为公共属性,因此我无法调用该方法。但我猜测当我执行实例复制时,该方法应该被直接调用。

那怎么样呢?

提前感谢您。


通过执行 tempObject = jsonObjecttempObject 保留了相同的对象引用...你需要执行 tempObject = jsonObject.clone() - fmodos
JsonObject 中没有 clone() 方法,因此我无法执行 jsonObject.clone()。 - otnieldocs
如果您不需要使用gson,可以使用json-io(https://github.com/jdereg/json-io)来克隆Java对象:`return JsonReader.jsonToJava(JsonWriter.objectToJson(root))`。它可以在Maven Central上获得。 - John DeRegnaucourt
5个回答

13
这可以用于复制任何类型的对象!只需要使用Gson。
public <T> T deepCopy(T object, Class<T> type) {
    try {
        Gson gson = new Gson();
        return gson.fromJson(gson.toJson(object, type), type);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

在您的情况下,您可以这样调用:

JsonObject jsonObject = deepCopy(oldJsonObject, JsonObject.class);

注意:每次调用深拷贝时不应重新构建gson。 - AnthonyJClink
1
不要错过下面的解决方案,deepCopy()现在是JsonElement的公共方法。不再需要任何技巧。 - InI4

11
从版本2.8.2开始,Gson中的JsonElement中的deepCopy()方法已经变为公共方法,因此您现在可以使用它来制作您的JSON对象的深度拷贝。

3

看起来Gson的开发者决定不公开deepCopy()。你可以将你的 JsonElement 序列化成字符串再转换,但是我认为在JsonObject之外实现深度克隆会更加高效。这是我的解决方案:

@Nonnull
public static JsonObject deepCopy(@Nonnull JsonObject jsonObject) {
    JsonObject result = new JsonObject();
    for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
        result.add(entry.getKey(), deepCopy(entry.getValue()));
    }
    return result;
}

@Nonnull
public static JsonArray deepCopy(@Nonnull JsonArray jsonArray) {
    JsonArray result = new JsonArray();
    for (JsonElement e : jsonArray) {
        result.add(deepCopy(e));
    }
    return result;
}

@Nonnull
public static JsonElement deepCopy(@Nonnull JsonElement jsonElement) {
    if (jsonElement.isJsonPrimitive() || jsonElement.isJsonNull()) {
        return jsonElement;       // these are immutables anyway
    } else if (jsonElement.isJsonObject()) {
        return deepCopy(jsonElement.getAsJsonObject());
    } else if (jsonElement.isJsonArray()) {
        return deepCopy(jsonElement.getAsJsonArray());
    } else {
        throw new UnsupportedOperationException("Unsupported element: " + jsonElement);
    }
}

它在2.8.2中暴露出来,根据JaakkoK最近的回答。 - Robert Fleming

3

tempObject = jsonObject 设置不会为您创建第二个对象。所有它所做的就是为您的原始jsonObject创建另一个引用。

你想要做的是像这样:

JSONObject tempObject = new JSONObject(jsonObject.toString());
tempObject.remove("children");

这将创建一个新的JsonObject,它是原始json的副本。
如果只能使用GSON库,则可以使用JsonObject.deepyCopy()方法。该方法在r855中添加:https://code.google.com/p/google-gson/source/detail?r=855 使用deepCopy()方法如下:
JsonObject tempObject = jsonObject.deepCopy();
tempObject.remove("children");

1
JsonObject的构造函数只有空构造函数,没有任何参数。因此,您的方法无法在JsonObject中完成。我也尝试过,但是在查看JsonObject类定义后,发现没有接受参数的构造函数。 - otnieldocs
你有访问这个API的权限吗? http://www.json.org/javadoc/org/json/JSONObject.html#JSONObject%28org.json.JSONObject,%20java.lang.String%5B%5D%29 GSON是建立在JSON库之上的,因此您仍然可以访问公共的JSONObject(JSONObject jo, java.lang.String[] names)构造函数。 - Andy Guibert
1
如果您使用Gson中的JsonObject,那么它没有像json.org中的JSONObject那样的构造函数。这是不同的Gson解析器。 - otnieldocs
看起来他们在r855的gson库中添加了一个jsonObject.deepCopy()。请参见此处:https://code.google.com/p/google-gson/source/detail?r=855 如果您使用的是旧版本的GSON库,您可以将此方法复制到帮助程序方法中,这样就可以得到您想要的内容。 - Andy Guibert
2
deepCopy方法是私有的!不能在其范围之外使用,您可以使用Gson将jsonObject解析为字符串,然后再使用Gson将该字符串读入另一个对象中,如果有人需要,我会发布完整的解决方案。 - Muhammad Alfaifi
显示剩余3条评论

1
我已经找到了最简单的解决方案。由于JsonObject的deepCopy()方法似乎无法正常工作,所以我只需将JsonObject值转换为字符串,然后使用JsonParser()将其转换为JsonElement。 然后从我们的新JsonObject创建一些新的JsonObject。这似乎比创建一些需要重新实现deepCopy的帮助程序方法更简单。如果我们重新实现迭代,必须考虑JsonObject的深度。由于JsonObject是哈希映射(LinkedTreeMap),而值是JsonElement,因此需要通过JsonElement进行递归解析。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接