JsonPath操作JSON

引言

假设想要解析JSON对象且JSON的格式不固定,最开始的做法是转换Json对象为Map对象,然后一层层遍历Map(例如使用Gson),那么有没有类似XML中的xpath解析表达式的工具?最终发现了json-path工具。
参考链接:github json-path
Maven依赖:

 <dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.4.0</version>
</dependency>

该工具提供了类似XPATH的层级似的JSON解析功能,同时提供读取、写入的功能。

JsonPath语法

Operators

OperatorDescription
$The root element to query. This starts all path expressions.
@The current node being processed by a filter predicate.
*Wildcard. Available anywhere a name or numeric are required.
..Deep scan. Available anywhere a name is required.
.<name>Dot-notated child
['<name>' (, '<name>')]Bracket-notated child or children
[<number> (, <number>)]Array index or indexes
[start:end]Array slice operator
[?(<expression>)]Filter expression. Expression must evaluate to a boolean value.

Functions

Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression.
The function output is dictated by the function itself.

FunctionDescriptionOutput
min()Provides the min value of an array of numbersDouble
max()Provides the max value of an array of numbersDouble
avg()Provides the average value of an array of numbersDouble
stddev()Provides the standard deviation value of an array of numbersDouble
length()Provides the length of an arrayInteger
sum()Provides the sum value of an array of numbersDouble

Filter Operators

Filters are logical expressions used to filter arrays. A typical filter would be [?(@.age > 18)] where @ represents the current item being processed. More complex filters can be created with logical operators && and ||. String literals must be enclosed by single or double quotes ([?(@.color == ‘blue’)] or [?(@.color == “blue”)]).

OperatorDescription
==left is equal to right (note that 1 is not equal to '1')
!=left is not equal to right
<left is less than right
<=left is less or equal to right
>left is greater than right
>=left is greater than or equal to right
=~left matches regular expression [?(@.name =~ /foo.*?/i)]
inleft exists in right [?(@.size in ['S', 'M'])]
ninleft does not exists in right
subsetofleft is a subset of right [?(@.sizes subsetof ['S', 'M', 'L'])]
anyofleft has an intersection with right [?(@.sizes anyof ['M', 'L'])]
noneofleft has no intersection with right [?(@.sizes noneof ['M', 'L'])]
sizesize of left (array or string) should match right
emptyleft (array or string) should be empty

JsonPath语法示例

Given the 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
            },
            {
                "category": "fiction",
                "author": "Herman Melville",
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            },
            {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    },
    "expensive": 10
}

JsonPath Result
$.store.book[*].authorThe authors of all books
$..authorAll authors
$.store.*All things, both books and bicycles
$.store..priceThe price of everything
$..book[2]The third book
$..book[-2]The second to last book
$..book[0,1]The first two books
$..book[:2]All books from index 0 (inclusive) until index 2 (exclusive)
$..book[1:2]All books from index 1 (inclusive) until index 2 (exclusive)
$..book[-2:]Last two books
$..book[2:]Book number two from tail
$..book[?(@.isbn)]All books with an ISBN number
$.store.book[?(@.price < 10)]All books in store cheaper than 10
$..book[?(@.price <= $['expensive'])]All books in store that are not "expensive"
$..book[?(@.author =~ /.*REES/i)]All books matching regex (ignore case)
$..*Give me every thing
$..book.length()The number of books

JsonPath代码示例

解析并修改如下JSON(示例为Istio中的某个VirtualService定义):

{
  "kind": "VirtualService",
  "apiVersion": "networking.istio.io/v1alpha3",
  "metadata": {
    "name": "my-svc",
    "namespace": "tsp"
  },
  "spec": {
    "hosts": [
      "s106.tsp.svc.cluster.local",
      "my-svc.tsp.svc.cluster.local"
    ],
    "gateways": [
      "mesh",
      "my-svc"
    ],
    "http": [
      {
        "match": [
          {
            "headers": {
              "user-id": {
                "regex": "11|22|33"
              }
            },
            "uri": {
              "prefix": "/256"
            }
          }
        ],
        "rewrite": {
          "uri": "/my_svc"
        },
        "route": [
          {
            "destination": {
              "host": "s106.tsp.svc.cluster.local",
              "subset": "v257"
            }
          }
        ]
      },
      {
        "match": [
          {
            "uri": {
              "prefix": "/256"
            }
          }
        ],
        "rewrite": {
          "uri": "/my_svc"
        },
        "route": [
          {
            "destination": {
              "host": "s106.tsp.svc.cluster.local",
              "subset": "v256"
            }
          }
        ]
      },
      {
        "route": [
          {
            "destination": {
              "host": "s106.tsp.svc.cluster.local",
              "subset": "v256"
            }
          }
        ]
      }
    ],
    "tcp": null,
    "tls": null
  }
}
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;
import com.svc.mgt.utils.JsonUtils;
import net.minidev.json.JSONArray;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Map;

import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;

/**
 * K8s查询结果Json转换 - 测试
 *
 * @Ahthor luohq
 * @Date 2020-07-20
 */

@RunWith(PowerMockRunner.class)  //使用PowerMockRunner运行时
@PowerMockIgnore({"javax.management.*"}) //忽略一些mock异常
public class K8sJsonResultTest {
    private static final Logger logger = LogManager.getLogger(K8sJsonResultTest.class);
    @Test
    public void testVsJson() {
        String vsResult = "...";
        /** JsonPath解析 */
        final DocumentContext ctx = JsonPath.parse(vsResult);
        
        /** 基础路由过滤器 - 包含http[].match 且 不包含有http[].match[0].headers*/
        Filter baseRouteFilter = filter(
                where("$.match").exists(true).and("$.match[0].headers").exists(false)
        );

        /** 读取属性(指定过滤器)*/
        JSONArray httpRoutes = ctx.read("$.spec.http[*]", baseRouteFilter);
        for (Object httpRoute : httpRoutes) {
            String subset = JsonPath.read(httpRoute, "$.route[0].destination.subset");
            logger.info("baseRoute -> subset={}, httpRoute={}", subset, JsonPath.parse(httpRoute).jsonString());
        }
        /** 读取属性 */
        Map httpRouteObj = ctx.read("$.spec.http[0]");
        
        //替换user-id | product-id
        String headersJson = String.format("{\"user-id\": {\"regex\": \"%s\"},\"product-id\": {\"exact\":\"%s\"}}", "111|222", "12342");
        /** 设置属性 - 重置baseRoute中的match.headers(add添加数组元素、set设置属性、put新增或修改属性) */
        JsonPath.parse(httpRouteObj).put("$.match[0]", "headers", JsonUtils.fromJson(headersJson, Map.class));
        logger.info("svcRouteRule: {}", JsonPath.parse(httpRouteObj).jsonString());
        logger.info("vs.ctx={}", ctx.jsonString());

        /** 删除对象 - 清空 $.spec.http数组 */
        ctx.delete("$.spec.http.*");
        logger.info("vs.ctx={}", ctx.jsonString());

    }
    
}

运行结果:

2020-07-21 14:49:39.389 INFO [main][K8sJsonResultTest.java:146] - baseRoute -> subset=v256, httpRoute={"match":[{"uri":{"prefix":"/256"}}],"rewrite":{"uri":"/mx_tour_app"},"route":[{"destination":{"host":"s106.tsp.svc.cluster.local","subset":"v256"}}]}
2020-07-21 14:49:39.488 INFO [main][K8sJsonResultTest.java:154] - svcRouteRule: {"match":[{"headers":{"user-id":{"regex":"111|222"},"product-id":{"exact":"12342"}},"uri":{"prefix":"/256"}}],"rewrite":{"uri":"/mx_tour_app"},"route":[{"destination":{"host":"s106.tsp.svc.cluster.local","subset":"v257"}}]}
2020-07-21 14:49:39.488 INFO [main][K8sJsonResultTest.java:155] - vs.ctx={"kind":"VirtualService","apiVersion":"networking.istio.io/v1alpha3","metadata":{"name":"my-app","namespace":"tsp","selfLink":"/apis/networking.istio.io/v1alpha3/namespaces/tsp/virtualservices/my-app","uid":"368f02de-ac7b-11ea-b7d7-fa163e432d7e","resourceVersion":"194978865","generation":11,"creationTimestamp":"2020-06-12T07:06:18Z"},"spec":{"hosts":["s106.tsp.svc.cluster.local","my-app.tsp.svc.cluster.local","my-app-pre.xxxx.com"],"gateways":["mesh","my-app"],"http":[{"match":[{"headers":{"user-id":{"regex":"111|222"},"product-id":{"exact":"12342"}},"uri":{"prefix":"/256"}}],"rewrite":{"uri":"/mx_tour_app"},"route":[{"destination":{"host":"s106.tsp.svc.cluster.local","subset":"v257"}}]},{"match":[{"uri":{"prefix":"/256"}}],"rewrite":{"uri":"/mx_tour_app"},"route":[{"destination":{"host":"s106.tsp.svc.cluster.local","subset":"v256"}}]},{"route":[{"destination":{"host":"s106.tsp.svc.cluster.local","subset":"v256"}}]}],"tcp":null,"tls":null}}
2020-07-21 14:49:39.489 INFO [main][K8sJsonResultTest.java:160] - vs.ctx={"kind":"VirtualService","apiVersion":"networking.istio.io/v1alpha3","metadata":{"name":"my-app","namespace":"tsp","selfLink":"/apis/networking.istio.io/v1alpha3/namespaces/tsp/virtualservices/my-app","uid":"368f02de-ac7b-11ea-b7d7-fa163e432d7e","resourceVersion":"194978865","generation":11,"creationTimestamp":"2020-06-12T07:06:18Z"},"spec":{"hosts":["s106.tsp.svc.cluster.local","my-app.tsp.svc.cluster.local","my-app-pre.xxxx.com"],"gateways":["mesh","my-app"],"http":[],"tcp":null,"tls":null}}

JsonPath是一个用于从JSON文档中提取信息的库。在Java中,我们通常使用JsonPath来解析JSON串,它允许我们通过路径表达式来访问JSON对象中的数据,类似于我们在文件系统中使用路径来定位文件一样。 在Java中使用JsonPath解析JSON串通常涉及以下几个步骤: 1. 添加JsonPath依赖:首先需要在项目中添加JsonPath库的依赖。如果你使用Maven,可以在pom.xml文件中添加相应的依赖项。 2. 解析JSON文档:将JSON字符串解析为一个可操作JSON文档对象,通常使用诸如`JSONObject`或`JsonNode`等类。 3. 应用JsonPath表达式:使用JsonPath表达式来指定你想要从JSON文档中提取的数据的路径。 4. 执行查询:根据提供的JsonPath表达式执行查询,返回查询结果。 下面是一个使用JsonPath解析JSON串的简单示例代码: ```java import com.jayway.jsonpath.JsonPath; import java.util.List; public class JsonPathExample { public static void main(String[] args) { // 假设我们有一个JSON串 String jsonStr = "{\"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}]}}"; // 使用JsonPath读取所有书的标题 List<String> titles = JsonPath.read(jsonStr, "$.store.book[*].title"); for (String title : titles) { System.out.println(title); } // 使用JsonPath读取第一本书的价格 Double firstBookPrice = JsonPath.read(jsonStr, "$.store.book[0].price"); System.out.println(firstBookPrice); } } ``` 通过上面的例子,你可以看到如何使用JsonPath在Java中对JSON串进行解析和提取信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗小爬EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值