JsonPath使用(Java)

发布时间 2023-10-18 14:30:34作者: tomoncle

JsonPath使用(Java)

Java有一些类似于jq的语法库和工具。其中一个叫做JsonPath,它允许使用类似于jq的语法来查询和操作JSON数据。可以使用JsonPath来提取特定的JSON字段、过滤数据、执行计算等操作。另外,还有一些其他的Java库和框架也提供了类似的功能,比如FastJson,Gson和Jackson。这些库可以在Java中更方便地处理和操作JSON数据。

这里只写常用的两种JsonPath和FastJson的使用示例(相对而言,fastjson功能更为强大,语法更丰富)。

JsonPath

用于读取 JSON 文档的 Java DSL。

语法

更多:https://raw.githubusercontent.com/json-path/JsonPath/8a0d2fd594b4a1baab71132cf5ef74889b9976e5/README.md

JSONPATH 描述
$ 根对象,例如 $.name.
@ 处理当前节点的表达式.例如 $.users[?(@.age > 26)]
* 通配符,对象的所有属性,例如$.leader.*
.. deepScan 属性访问,例如 $..name
.<name> 调用当前节点的子项,例如 $.name
['<name>' (, '<name>')] 多个属性访问。例如$['id','name']
[<number> (, <number>)] 数组多个元素访问,其中num是数字,可以是负数,返回数组中的多个元素。例如$[0,3,-2,5]
[start:end] 数组范围访问,其中 start 和 end 是开始小表和结束下标,可以是负数,返回数组中的多个元素。例如 $[0:5]
[?(<expression>)] 过滤表达式,必须为Boolean类型

代码示例

public class TestCase {
    @Test
    public void json() {
        String json = "{\"name\": \"John\", \"age\": 30}";

        ReadContext ctx = JsonPath.parse(json);
        String name = ctx.read("$.name");
        int age = ctx.read( "$.age");

        System.out.println("name: " + name + " ; age: " + age);

        json = "[\"jack\",\"tom\"]";
        ctx = JsonPath.parse(json);
        System.out.println(ctx.jsonString()+ " - "+ ctx.read("$.[0]"));
    }

    @Test
    public void array() {
        String json = "{\"users\": [{\"name\": \"John\", \"age\": 30}, {\"name\": \"Jane\", \"age\": 25}]}";
        ReadContext ctx = JsonPath.parse(json);
        // 使用JsonPath表达式查询数组数据:
        // 使用JsonPath表达式$.users[*].name和$.users[*].age分别从JSON数组中提取了所有用户的姓名和年龄。
        // 使用了通配符[*]来表示所有数组元素
        List<String> names = ctx.read( "$.users[*].name");
        System.out.println(names); // ["John","Jane"]

        String name = ctx.read( "$.users[0].name");
        System.out.println(name); // John

        // 使用JsonPath表达式$.users[0] 来查询数组第一个元素
        Map<String, Object> user = ctx.read( "$.users[0]");
        System.out.println(user); // {name=John, age=30}

        // 迭代数组:
        // 可以使用JsonPath表达式迭代JSON数组并执行其他操作。
        // 例如,以下示例使用JsonPath表达式迭代JSON数组并计算所有用户的平均年龄:
        List<Integer> ages = ctx.read( "$.users[*].age");
        double averageAge = ages.stream().mapToInt(Integer::intValue).average().orElse(0.0);
        System.out.println(averageAge); // 27.5
    }

    @Test
    public void arrayAndCompare() {
        String json = "{\n" +
                "  \"users\": [\n" +
                "    {\n" +
                "      \"name\": \"John\",\n" +
                "      \"age\": 30\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"Jane\",\n" +
                "      \"age\": 25\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"Bob\",\n" +
                "      \"age\": 40\n" +
                "    }\n" +
                "  ]\n" +
                "}";
        ReadContext ctx = JsonPath.parse(json);

        // 获取所有年龄大于30岁的用户的姓名。使用JsonPath表达式的条件语句来实现
        // JsonPath表达式$.users[?(@.age > 30)].name来获取所有年龄大于30岁的用户的姓名。
        // 请注意,条件语句[?(@.age > 30)]来判断每个用户的年龄是否大于30岁,并只获取符合条件的用户的姓名。
        // 可以使用其他条件语句来实现更复杂的逻辑,例如逻辑运算符(如AND和OR)和比较运算符(如等于和不等于)。
        List<String> names = ctx.read( "$.users[?(@.age > 30)].name");
        System.out.println(names); // ["Bob"]

        // JsonPath表达式不支持使用表达式后提取数组下标,如:$.users[?(@.age > 30)][0]
        // https://github.com/json-path/JsonPath/issues/272, 可以使用 fastjson 实现,fastjson支持该语法
        names = ctx.read( "$.users[?(@.age >= 30)][0]"); // 这个表达式现在不支持
        System.out.println(names); // []

        // 如果想使用
        names = ctx.read( "$.users[?(@.age > 30)]");
        System.out.println(names); // [{"name":"Bob","age":40}]

        Map<String, Object> user = ctx.read( "$.users[0]");
        System.out.println(user); // {name=John, age=30}

        // 获取所有年龄大于30岁的用户中的第一个姓名。由于JsonPath不支持表达式后直接使用数组下标,只能使用Java代码处理
        // 使用JsonPath表达式$.users[?(@.age > 30)].name来获取所有年龄大于30岁的用户的姓名,并使用Java代码来处理结果
        names = ctx.read("$.users[?(@.age > 30)].name");
        String firstName = names.isEmpty() ? null : names.get(0);
        System.out.println(firstName); // Bob

        // 获取所有年龄大于50岁的用户的姓名,并在数组为空时返回默认值"jack",否则返回姓名数组中的第一个值
        names = ctx.read( "$.users[?(@.age > 50)].name");
        firstName = names.isEmpty() ? "jack" : names.get(0);
        System.out.println(names); // []
        System.out.println(firstName); // jack
    }
}

FastJson

fastjson 1.2.0之后的版本支持JSONPath。可以在java框架中当作对象查询语言(OQL)来使用。

语法

JSONPATH描述
$根对象,例如 $.name
[num]数组访问,其中 num 是数字,可以是负数。例如 $[0].leader.departments[-1].name
[num0,num1,num2...]数组多个元素访问,其中 num 是数字,可以是负数,返回数组中的多个元素。例如 $[0,3,-2,5]
[start:end]数组范围访问,其中 start 和 end 是开始小表和结束下标,可以是负数,返回数组中的多个元素。例如 $[0:5]
[start:end :step]数组范围访问,其中 start 和 end 是开始小表和结束下标,可以是负数;step 是步长,返回数组中的多个元素。例如 $[0:5:2]
[?(key)]对象属性非空过滤,例如 $.departs[?(name)]
[key> 123]数值类型对象属性比较过滤,例如 $.departs[id>= 123],比较操作符支持 =,!=,>,>=,<,<=
[key = '123']字符串类型对象属性比较过滤,例如 $.departs[name = '123'],比较操作符支持 =,!=,>,>=,<,<=
[key like 'aa%']字符串类型 like 过滤,
例如 $.departs[name like 'sz*'],通配符只支持 %
支持 not like
[key rlike 'regexpr']字符串类型正则匹配过滤,
例如 departs[name rlike 'aa(.)*'],
正则语法为 jdk 的正则语法,支持 not rlike
[key in ('v0', 'v1')]IN 过滤, 支持字符串和数值类型
例如:
$.departs[name in ('wenshao','Yako')]
$.departs[id not in (101,102)]
[key between 234 and 456]BETWEEN 过滤, 支持数值类型,支持 not between
例如:
$.departs[id between 101 and 201]
$.departs[id not between 101 and 201]
length() 或者 size()数组长度。例如 $.values.size()
支持类型 java.util.Map 和 java.util.Collection 和数组
keySet()获取 Map 的 keySet 或者对象的非空属性名称。例如 $.val.keySet()
支持类型:Map 和普通对象
不支持:Collection 和数组(返回 null)
.属性访问,例如 $.name
..deepScan 属性访问,例如 $..name
*对象的所有属性,例如 $.leader.*
['key']属性访问。例如 $['name']
['key0','key1']多个属性访问。例如 $['id','name']

语法示例

JSONPath语义
$根对象
$[-1]最后元素
$[:-2]第 1 个至倒数第 2 个
$[1:]第 2 个之后所有元素
$[1,2,3]集合中 1,2,3 个元素

代码示例

public class TestCase {

    private String json = "{\"users\": [{\"name\": \"John\", \"age\": 30}, {\"name\": \"Jane\", \"age\": 25}]}";

    @Test
    public void json() {
        JSONObject jsonObject = JSON.parseObject(json);
        JSONArray users = (JSONArray) JSONPath.eval(jsonObject, "$.users");
        System.out.println(users); // [{"name":"John","age":30},{"name":"Jane","age":25}]

        JSONArray names = (JSONArray) JSONPath.eval(jsonObject, "$.users.name");
        System.out.println(names); // ["John","Jane"]

        String name = (String) JSONPath.eval(jsonObject, "$.users[1].name");
        System.out.println(name); // Jane
    }

    /**
     * use && and || to combine multiple predicates
     * [?(@.price < 10 && @.category == 'fiction')] , [?(@.category == 'reference' || @.price > 10)].
     *
     * use ! to negate a predicate
     * [?(!(@.price < 10 && @.category == 'fiction'))].
     */
    @Test
    public void array() {
        JSONObject jsonObject = JSON.parseObject(json);

        JSONArray users = (JSONArray) JSONPath.eval(jsonObject, "$.users[?(@.age > 26)]");
        System.out.println("年龄大于26的 user:" + users); // [{"name":"John","age":30}]

        users = (JSONArray) JSONPath.eval(jsonObject, "$.users[?(@.age > 20 && @.age < 30)]");
        System.out.println("年龄大于20并且小于30的 user:" + users); // [{"name":"Jane","age":25}]

        users = (JSONArray) JSONPath.eval(jsonObject, "$.users[?(@.age > 20 && @.name like 'Jo%')]");
        System.out.println("年龄大于20并且name以'Jo'开头的 user:" + users); // [{"name":"John","age":30}]

        JSONObject user = (JSONObject) JSONPath.eval(jsonObject, "$.users[?(@.age > 20)][0]");
        System.out.println("年龄大于20的第一个 user:" + user); // {"name":"John","age":30}

        JSONArray names = (JSONArray) JSONPath.eval(jsonObject, "$.users[?(@.age > 26)].name");
        System.out.println("年龄大于26的 name:" + names); // ["John"]

        names = (JSONArray) JSONPath.eval(jsonObject, "$.users[?(@.age >= 20)].name");
        System.out.println("年龄大于20的 name:" + names); // ["John","Jane"]

        String name = (String) JSONPath.eval(jsonObject, "$.users[?(@.age >= 20)][1].name");
        System.out.println("年龄大于20的第一个name:" + name); // Jane
    }
}