问题 使用GSON修改json而不使用POJO


我想修改json内容而不将其转换为POJO。我正在使用GSON Library。

以下是用例:

String jsonString = "[{\"key1\":\"Hello\",\"key2\":\"World\"},{\"key1\":\"Nice\",\"key2\":\"Town\"}]";

JsonElement jsonElement = gson.fromJson(jsonString, JsonElement.class);     

有没有什么方法可以将key1的值设置为每个数组中的某个值(比如说“Test”),而不将事物转换为POJO


4812
2018-01-24 10:46


起源



答案:


这是我想出的最短的。

JsonElement je = gson.fromJson(jsonString, JsonElement.class);
JsonObject jo = je.getAsJsonObject();
jo.add("key", value);

一旦你有了JsonObject,gson有很多方法来操作它。


7
2018-04-10 05:01



请你发布完整的解决方案,我不明白gson对象是如何创建的? - vikramvi


答案:


这是我想出的最短的。

JsonElement je = gson.fromJson(jsonString, JsonElement.class);
JsonObject jo = je.getAsJsonObject();
jo.add("key", value);

一旦你有了JsonObject,gson有很多方法来操作它。


7
2018-04-10 05:01



请你发布完整的解决方案,我不明白gson对象是如何创建的? - vikramvi


一种方法是将JSON转换为 java.util.Map,修改Map,然后从那里开始(这可能意味着将Map序列化为JSON)。

这种方法符合我的偏好,可以使用正确的API来完成正确的工作,最大限度地减少使用像Gson这样的工具来处理序列化/反序列化(这是我理解它的设计目的)。也就是说,不要将Gson API用作替换数据结构。


2
2018-01-24 16:21



你如何将JSON转换为Map? - linuxeasy


您始终可以获得与JsonElement不同的类型,或使用JsonElement.getAsJsonObject强制转换为Object(如果可能)。

String jsonString = "[{\"key1\":\"Hello\",\"key2\":\"World\"}, ...]";

JsonArray jsonArray = gson.fromJson(jsonString, JsonElement.class).getAsJsonArray();
JsonObject firstObject = jsonArray.get(i).getAsJsonObject();
firstObject.addProperty("key1", "Test");

我早先错了;似乎没有JsonArray适配器;你必须得到一个JsonElement并使用铸造工具。


1
2018-01-24 17:25





GSON有两个独立的API(可以组合):一个用于序列化和反序列化,另一个用于流式传输。如果您想要处理JSON流而没有内存开销或使用动态结构(而不​​是静态POJO),您可以执行以下操作:

  • 创建一个JsonWriter(在我的例子中我使用StringWriter);
  • 创建一个JsonReader;
  • 制作一个循环,消耗来自读者的事件并将它们提供给作者,可能会进行更改,添加,遗漏等。

循环将由一个switch语句组成,该语句必须包含所有可能事件(其中10个)。即使最简单的例子也必须包含所有这些,所以下面的代码看起来相当冗长。但它很容易扩展,进一步扩展不会延长。

附加“test”的示例:每个对象的1对看起来像:

public class Whatever {

static void streamandmodify(JsonReader reader, JsonWriter writer) throws IOException {
    while (true) {
        JsonToken token = reader.peek();
        switch (token) {
        // most cases are just consume the event
        // and pass an identical one to the writer
        case BEGIN_ARRAY:
            reader.beginArray();
            writer.beginArray();
            break;
        case END_ARRAY:
            reader.endArray();
            writer.endArray();
            break;
        case BEGIN_OBJECT:
            reader.beginObject();
            writer.beginObject();

            // this is where the change happens:
            writer.name("test");
            writer.value(1);
            break;
        case END_OBJECT:
            reader.endObject();
            writer.endObject();
            break;
        case NAME:
            String name = reader.nextName();
            writer.name(name);
            break;
        case STRING:
            String s = reader.nextString();
            writer.value(s);
            break;
        case NUMBER:
            String n = reader.nextString();
            writer.value(new BigDecimal(n));
            break;
        case BOOLEAN:
            boolean b = reader.nextBoolean();
            writer.value(b);
            break;
        case NULL:
            reader.nextNull();
            writer.nullValue();
            break;
        case END_DOCUMENT:
            return;
        }
    }
}


public static void main(String[] args) throws IOException {
    // just for test:
    JsonReader jr = new JsonReader(new StringReader("{\"a\":1, \"b\":{\"c\":[1,2,3,{},{}]}}"));
    StringWriter sw = new StringWriter();
    JsonWriter jw = new JsonWriter(sw);
    streamandmodify(jr, jw);
    System.out.println(sw.getBuffer().toString());
}
}

0
2018-01-24 17:14



JsonWriter可以做的很好的例子,这是肯定的。但是对于OP的任务,没有理由通过流式重新创建JSON对象而不是使用经过良好测试的gson.fromJson()。见杰夫的回答。 - teejay
@teejay:“没理由”?你怎么知道的?为何如此评论?我可以找到一些很好的理由,不构建任意大型JSON文档的整个内存中表示只是为了添加一些属性,比如将整个序列序列化回文件。我们都不知道问题的背景,因此流线型和笨拙的方法似乎可能更好。 - fdreger
好吧,我假设参与此页面的人想要解决他们的问题 - 即从已经完全可用的字符串修改JSON(=没有流媒体的理由)。你的答案“有效”,但在另一种情况下会更合适。没有冒犯的意思 :) - teejay
@teejay:我明白你是“人民”的声音?并以某种方式从作者的脑海中读出问题的确切背景?那么,在这种情况下 - 我很抱歉写这样的答案,它不会再发生。但请允许我为自己辩护:在我居住的陌生的土地上,Java应用程序主要使用JSON与外部系统进行通信,使用网络或文件。我使用的外来版本的java.io和javax.servlet包含的几个方法使得数据只是作为字符串弹出到内存中,这迫使我使用流。我真的,真的以为我并不孤单。 - fdreger
我看到你是流媒体人:)我们只是假设“默认情况下”不同的情境,那没关系。只是想知道这种方法有什么缺点..你曾经遇到过性能方面的问题吗?或者输出JSON无效?你有单元测试?如何迁移到更新的GSON版本,它是否至少有一点面向未来? - teejay


jsonString是一个简单的普通Java String;因此,您可以使用Java的标准字符串函数修改它,并替换子字符串 key1 同 Test1

jsonString = "[{\"key1\":\"Test\",\"key2\":\"World\"},{\"key1\":\"Nice\",\"key2\":\"Town\"}]";

当然,Java中的String是不可变的,因此首先将其转换为StringBuilder可能会在内存使用方面提供更好的性能。


0
2018-01-24 17:24



这在测试代码中是一个体面的hacky策略,但如果原始或字符串或替换是用户提供的,请在生产中小心。 JavaScript(和扩展的JSON)在处理转义和特殊字符时非常复杂。您可能希望让经过良好测试的库在生产中编辑您的JSON。 - Jeff Bowman
可能你的问题是错的。我有兴趣修改key1的值而不是key1本身。所以在第一种情况下,key1的值是Hello,我希望修改它。 - linuxeasy
哦,答案仍然是一样的;这只是必须改变的例子。 - SylvainL
我认为,进一步讨论没有意义。如果你的答案保持不变,那么我就无法管理动态值,这可能是任何事情,而我不知道应该用什么和何时替换。 - linuxeasy
我不是很熟悉Java的字符串函数,也不熟悉json格式,但这不应该是任何人的主要问题。例如,您可以搜索bot子字符串“key1”:“and,”key2“(包括逗号和引号);知道您必须替换它们之间的内容,或者您​​可以搜索”key1“:”然后扫描每个字符,直到你找到终端双引号“,考虑到任何嵌入的双引号前面都会有转义字符\。解决方案将取决于你的jsontString的复杂性,但只有你知道这个参数。 - SylvainL