寻找 Json-path/(任何 API)来更新 Java 中给定 json 字符串中的任何值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25407420/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 07:59:13  来源:igfitidea点击:

Looking for Json-path/(any API) to update any value in given json string in Java

javajson

提问by JLP

Inshort : I am trying to find some api that could just change the value by taking first parameter as jsonString , second parameter as JSONPath and third will be new value of that parameter. But, all I found is this.. https://code.google.com/p/json-path/

简而言之:我试图找到一些可以通过将第一个参数作为 jsonString ,第二个参数作为 JSONPath 和第三个参数的新值来改变值的 API。但是,我发现的只是这个.. https://code.google.com/p/json-path/

This api allows me to find any value in JSON String. But, I am not finding easy way to update the value of any key. For example, Here is a book.json.

这个 api 允许我在 JSON 字符串中找到任何值。但是,我没有找到更新任何键值的简单方法。例如,这是一个 book.json。

{
"store":{
    "book":[
        {
            "category":"reference",
            "author":"Nigel Rees",
            "title":"Sayings of the Century",
            "price":8.95
        },
        {
            "category":"fiction",
            "author":"Evelyn Waugh",
            "title":"Sword of Honour",
            "price":12.99,
            "isbn":"0-553-21311-3"
        }
    ],
    "bicycle":{
        "color":"red",
        "price":19.95
    }
   }
 }

I can access color of bicycle by doing this.

通过这样做,我可以访问自行车的颜色。

String bicycleColor = JsonPath.read(json, "$.store.bicycle.color");

But I am looking for a method in JsonPath or other api some thing like this

但我正在 JsonPath 或其他 api 中寻找类似这样的方法

    JsonPath.changeNodeValue(json, "$.store.bicycle.color", "green");
    String bicycleColor = JsonPath.read(json, "$.store.bicycle.color");
    System.out.println(bicycleColor);  // This should print "green" now. 

I am excluding these options,

我排除了这些选项,

  • Create a new JSON String.
  • Create a JSON Object to deal with changing value and convert it back to jsonstring
  • 创建一个新的 JSON 字符串。
  • 创建一个 JSON 对象来处理变化的值并将其转换回 jsonstring

Reason: I have about 500 different requests for different types of service which return different json structure. So, I do not want to manually create new JSON string always. Because, IDs are dynamic in json structure.

原因:我对不同类型的服务有大约 500 个不同的请求,它们返回不同的 json 结构。所以,我不想总是手动创建新的 JSON 字符串。因为,ID 在 json 结构中是动态的。

Any idea or direction is much appreciated.

任何想法或方向都非常感谢。

Updating this question with following answer.

使用以下答案更新此问题。

  1. Copy MutableJson.java.
  2. copy this little snippet and modify as per you need.

    private static void updateJsonValue() {
    
    JSONParser parser = new JSONParser();
    JSONObject jsonObject = new JSONObject();
    
    FileReader reader = null;
    try {
        File jsonFile = new File("path to book.json");
        reader = new FileReader(jsonFile);
        jsonObject = (JSONObject) parser.parse(reader);
    
    } catch (Exception ex) {
        System.out.println(ex.getLocalizedMessage());
    }
    
    Map<String, Object> userData = null;
    try {
        userData = new ObjectMapper().readValue(jsonObject.toJSONString(), Map.class);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    MutableJson json = new MutableJson(userData);
    
    System.out.println("Before:\t" + json.map());
    
    json.update("$.store.book[0].author", "jigish");
    json.update("$.store.book[1].category", "action");
    
    System.out.println("After:\t" + json.map().toString());
    
    }
    
  1. 复制MutableJson.java
  2. 复制这个小片段并根据需要进行修改。

    private static void updateJsonValue() {
    
    JSONParser parser = new JSONParser();
    JSONObject jsonObject = new JSONObject();
    
    FileReader reader = null;
    try {
        File jsonFile = new File("path to book.json");
        reader = new FileReader(jsonFile);
        jsonObject = (JSONObject) parser.parse(reader);
    
    } catch (Exception ex) {
        System.out.println(ex.getLocalizedMessage());
    }
    
    Map<String, Object> userData = null;
    try {
        userData = new ObjectMapper().readValue(jsonObject.toJSONString(), Map.class);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    MutableJson json = new MutableJson(userData);
    
    System.out.println("Before:\t" + json.map());
    
    json.update("$.store.book[0].author", "jigish");
    json.update("$.store.book[1].category", "action");
    
    System.out.println("After:\t" + json.map().toString());
    
    }
    

Use these libraries.

使用这些库。

  • import org.json.simple.JSONObject;
  • import org.json.simple.parser.JSONParser;
  • import org.codehaus.Hymanson.map.ObjectMapper;
  • 导入 org.json.simple.JSONObject;
  • 导入 org.json.simple.parser.JSONParser;
  • 导入 org.codehaus.Hymanson.map.ObjectMapper;

采纳答案by Vlad

Assuming that parsed JSON can be represented in memory as a Map, you can build an API similar to JsonPath that looks like:

假设解析后的 JSON 可以在内存中表示为 Map,您可以构建一个类似于 JsonPath 的 API,如下所示:

void update(Map<String, Object> json, String path, Object newValue);

I've quickly done a gist of a dirty implementation for simple specific paths (no support for conditions and wildcards) that can traverse json tree, E.g. $.store.name, $.store.books[0].isbn. Here it is: MutableJson.java. It definitely needs improvement, but can give a good start.

我已经快速完成了可以遍历 json 树的简单特定路径(不支持条件和通配符)的脏实现要点,例如 $.store.name、$.store.books[0].isbn。它是:MutableJson.java。它肯定需要改进,但可以提供一个良好的开端。

Usage example:

用法示例:

import java.util.*;

public class MutableJson {

    public static void main(String[] args) {
        MutableJson json = new MutableJson(
                new HashMap<String, Object>() {{
                    put("store", new HashMap<String, Object>() {{
                        put("name", "Some Store");
                        put("books", Arrays.asList(
                                new HashMap<String, Object>() {{
                                    put("isbn", "111");
                                }},
                                new HashMap<String, Object>() {{
                                    put("isbn", "222");
                                }}
                        ));
                    }});
                }}
        );

        System.out.println("Before:\t" + json.map());

        json.update("$.store.name", "Book Store");
        json.update("$.store.books[0].isbn", "444");
        json.update("$.store.books[1].isbn", "555");

        System.out.println("After:\t" + json.map());
    }

    private final Map<String, Object> json;

    public MutableJson(Map<String, Object> json) {
        this.json = json;
    }

    public Map<String, Object> map() {
        return json;
    }

    public void update(String path, Object newValue) {
        updateJson(this.json, Path.parse(path), newValue);
    }

    private void updateJson(Map<String, Object> data, Iterator<Token> path, Object newValue) {
        Token token = path.next();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            if (!token.accept(entry.getKey(), entry.getValue())) {
                continue;
            }

            if (path.hasNext()) {
                Object value = token.value(entry.getValue());
                if (value instanceof Map) {
                    updateJson((Map<String, Object>) value, path, newValue);
                }
            } else {
                token.update(entry, newValue);
            }
        }
    }
}

class Path {
    public static Iterator<Token> parse(String path) {
        if (path.isEmpty()) {
            return Collections.<Token>emptyList().iterator();
        }
        if (path.startsWith("$.")) {
            path = path.substring(2);
        }

        List<Token> tokens = new ArrayList<>();
        for (String part : path.split("\.")) {
            if (part.matches("\w+\[\d+\]")) {
                String fieldName = part.substring(0, part.indexOf('['));
                int index = Integer.parseInt(part.substring(part.indexOf('[')+1, part.indexOf(']')));
                tokens.add(new ArrayToken(fieldName, index));
            } else {
                tokens.add(new FieldToken(part));
            }
        };

        return tokens.iterator();
    }
}

abstract class Token {

    protected final String fieldName;

    Token(String fieldName) {
        this.fieldName = fieldName;
    }

    public abstract Object value(Object value);

    public abstract boolean accept(String key, Object value);

    public abstract void update(Map.Entry<String, Object> entry, Object newValue);
}

class FieldToken extends Token {

    FieldToken(String fieldName) {
        super(fieldName);
    }

    @Override
    public Object value(Object value) {
        return value;
    }

    @Override
    public boolean accept(String key, Object value) {
        return fieldName.equals(key);
    }

    @Override
    public void update(Map.Entry<String, Object> entry, Object newValue) {
        entry.setValue(newValue);
    }
}

class ArrayToken extends Token {

    private final int index;

    ArrayToken(String fieldName, int index) {
        super(fieldName);
        this.index = index;
    }

    @Override
    public Object value(Object value) {
        return ((List) value).get(index);
    }

    @Override
    public boolean accept(String key, Object value) {
        return fieldName.equals(key) && value instanceof List && ((List) value).size() > index;
    }

    @Override
    public void update(Map.Entry<String, Object> entry, Object newValue) {
        List list = (List) entry.getValue();
        list.set(index, newValue);
    }
}

A JSON string can be easily parsed into a Map using Hymanson:

使用 Hymanson 可以轻松地将 JSON 字符串解析为 Map:

Map<String,Object> userData = new ObjectMapper().readValue("{ \"store\": ... }", Map.class);

回答by Homyk

The thing is that the functionality you want is already an undocumented feature of JsonPath. Example using your json structure:

问题是您想要的功能已经是 JsonPath 的未记录功能。使用 json 结构的示例:

String json = "{ \"store\":{ \"book\":[ { \"category\":\"reference\", \"author\":\"Nigel Rees\", \"title\":\"Sayings of the Century\", \"price\":8.95 }, { \"category\":\"fiction\", \"author\":\"Evelyn Waugh\", \"title\":\"Sword of Honour\", \"price\":12.99, \"isbn\":\"0-553-21311-3\" } ], \"bicycle\":{ \"color\":\"red\", \"price\":19.95 } } }";
DocumentContext doc = JsonPath.parse(json).
    set("$.store.bicycle.color", "green").
    set("$.store.book[0].price", 9.5);
String newJson = new Gson().toJson(doc.read("$"));

回答by kdabir

Just answering for folks landing on this page in future for reference.

只是为将来登陆此页面的人回答以供参考。

You could consider using a Java implementation of jsonpatch. RFC can be found here

您可以考虑使用jsonpatch的 Java 实现。RFC可以在这里找到

JSON Patch is a format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed. When used in combination with the HTTP PATCH method it allows partial updates for HTTP APIs in a standards compliant way.

JSON Patch 是一种用于描述 JSON 文档更改的格式。当只有一部分发生变化时,它可用于避免发送整个文档。当与 HTTP PATCH 方法结合使用时,它允许以符合标准的方式对 HTTP API 进行部分更新。

You can specify the operation that needs to be performed (replace, add....), json path at which it has to be performed, and the value which should be used.

您可以指定需要执行的操作(替换、添加....)、必须执行的 json 路径以及应该使用的值。

Again, taking example from the RFC :

再次以 RFC 为例:

 [
     { "op": "test", "path": "/a/b/c", "value": "foo" },
     { "op": "remove", "path": "/a/b/c" },
     { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
     { "op": "replace", "path": "/a/b/c", "value": 42 },
     { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
     { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
   ]

For Java implementation, I have not used it myself, but you can give a try to https://github.com/fge/json-patch

Java的实现,我自己没用过,大家可以试试https://github.com/fge/json-patch

回答by durron597

So in order to change a value within a JSon string, there are two steps:

因此,为了更改 JSon 字符串中的值,有两个步骤:

  1. Parse the JSon
  2. Modify the appropriate field
  1. 解析 JSon
  2. 修改相应字段

You are trying to optimize step 2, but understand that you are not going to be able to avoid step 1. Looking at the Json-path source code (which, really, is just a wrapper around Hymanson), note that it does do a full parse of the Json string before being able to spit out the read value. It does this parse every time you call read(), e.g. it is not cached.

您正在尝试优化第 2 步,但要了解您将无法避免第 1 步。查看 Json-path 源代码(实际上,它只是Hymanson的包装器),请注意它确实做了一个在能够吐出读取值之前对 Json 字符串进行完整解析。每次调用read()时它都会进行解析,例如它不会被缓存。

I think this task is specific enough that you're going to have to write it yourself. Here is what I would do:

我认为此任务足够具体,您将不得不自己编写。这是我会做的:

  1. Create an object that represents the data in the parsed Json string.
    • Make sure this object has, as part of it's fields, the Json Stringpieces that you do not expect to change often.
  2. Create a custom Deserializer in the Json framework of your choice that will populate the fields correctly.
  3. Create a custom Serializer that uses the cached Stringpieces, plus the data that you expect to change
  1. 创建一个对象来表示解析后的 Json 字符串中的数据。
    • 确保此对象具有String您不希望经常更改的 Json片段作为其字段的一部分。
  2. 在您选择的 Json 框架中创建一个自定义反序列化器,以正确填充字段。
  3. 创建一个使用缓存String片段的自定义序列化程序,以及您希望更改的数据

I think the exact scope of your problem is unusual enough that it is unlikely a library already exists for this. When a program receives a Json String, most of the time what it wants is the fully deserialized object - it is unusual that it needs to FORWARD this object on to somewhere else.

我认为您的问题的确切范围很不寻常,因此不太可能已经存在用于此的库。当一个程序收到一个 Json 时String,大多数时候它想要的是完全反序列化的对象——它需要将此对象转发到其他地方是不寻常的。