JSONPATH-阿里和jayway的实现测试

业务业务的需要,所以想找一个从对象中获取属性的工具。

搜了搜发现由阿里和jayway的实现,又花费了一些时间了解和练习,总结了一些要点:

阿里的可能快一些,但考虑到完备性,也许选择jayway更好一些。

本文档参考了以下URL:

Jayway JsonPath介绍_com.jayway.jsonpath-CSDN博客

FASTJSON2 JSONPath支持介绍 | fastjson2

https://github.com/json-path/JsonPath

 

一、测试代码

1.1基本类TestJsonPath

用于生成测试数据:

package study.base.json;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import study.base.classes.ChineseMan;
import study.base.classes.Human;
import study.base.classes.Parent;

/**
 * JsonPath练习
 * @author lzfto
 */
public abstract class TestJsonPath {
    
    public String toJsonString(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();  
        String json;
        try {
            json = objectMapper.writeValueAsString(obj);
            return json;
        } catch (JsonProcessingException e) {
            return "";
        }  
        
    }
    public ChineseMan getJObject() {
        ChineseMan lzf = new ChineseMan();
        lzf.setName("lzf");
        lzf.setAge(12);
        lzf.setHeight(90);
        lzf.setHeight(170);
        lzf.set力量("大力");
        lzf.set斗志("昂扬");
        lzf.setType("传承");
        lzf.setSoldier(true);
        lzf.setGender("男");
        lzf.setBirthDay(new Date());
        lzf.setLocation("中国");
        Parent father = new Parent("鲁达", "男", 99, new BigDecimal("180"));
        Parent mather = new Parent("黄试", "女", 103, new BigDecimal("160"));
        lzf.setFather(father);
        lzf.setMother(mather);
        
        
        Human girl =new Human("女");
        girl.setName("lml");
        girl.setHeight(160);
        girl.setWeight(90);
        girl.setLocation("中国");
        girl.setType("创造");
        girl.setBirthDay(new Date());
        girl.setEatable(false);
        
        Human e =new Human("男");
        e.setName("lmj");
        e.setHeight(175);
        e.setWeight(140);
        e.setLocation("中国");
        e.setType("前瞻");
        e.setBirthDay(new Date());
        e.setEatable(false);
        lzf.getChildren().add(girl);
        lzf.getChildren().add(e);
        
        return lzf;
    }

    public List<ChineseMan> getObjectList() {
    
        final String[] names = { "楼上的", "非法手段", "口袋空空", "嗯嗯开始", "共和国和", "给v叫我", "说的话我", "很多我不怕", "皮肤和", "工期哈" };
        Random random = new Random();
        List<ChineseMan> list=new ArrayList<>();
    
        for (int i = 1; i <= 10; i++) {
            int namePos = random.nextInt(10); // 生成0到9之间的随机数
            String name = names[namePos];
            int age = random.nextInt(100);
            int height = random.nextInt(200);
            int weight = random.nextInt(100);
            String sex = random.nextInt(0,2) == 1 ? "男" : "女";
            ChineseMan jo = this.getRanddomObject(name, age, height, weight, sex);
            list.add(jo);
        }
    
        return list;
    }

    public ChineseMan getRanddomObject(String name, Integer age, Integer height, Integer weight, String sex) {
        Random random = new Random();
        ChineseMan lzf = new ChineseMan();
        lzf.setName(name);
        lzf.setAge(age);
        lzf.setHeight(height);
        lzf.setHeight(weight);
        lzf.set力量("大力");
        lzf.set斗志("昂扬");
        lzf.setType("传承");
        lzf.setSoldier(true);
        lzf.setGender(sex);
        lzf.setBirthDay(new Date());
        lzf.setLocation("中国");
        Parent father = new Parent(name + "爸", "男", 99, new BigDecimal("180"));
        Parent mather = new Parent(name + "妈", "女", 103, new BigDecimal("160"));
        lzf.setFather(father);
        lzf.setMother(mather);
        
        
        
        
        Human girl =new Human("女");
        girl.setName(name+"女");
        girl.setHeight(random.nextInt(155, 170));
        girl.setWeight(random.nextInt(80, 90));
        girl.setLocation("中国");
        girl.setType("创造");
        girl.setBirthDay(new Date());
        girl.setEatable(false);
        girl.setPower(random.nextInt(100, 400));
        
        Human e =new Human("男");
        e.setName(name+"男");
        e.setHeight(random.nextInt(155, 170));
        e.setWeight(random.nextInt(80, 99));
        e.setLocation("中国");
        e.setType("前瞻");
        e.setBirthDay(new Date());
        e.setEatable(false);
        e.setPower(random.nextInt(100, 900));
        lzf.getChildren().add(girl);
        lzf.getChildren().add(e);
        return lzf;
    }

    /**
     * 从JSON对象中一次获取多个key
     * 
     * @param jo       JSON对象
     * @param pathList 路径
     */
    public abstract void testReadMultiKeyOnce(Object jo, List<String> pathList);

    /**
     * 测试在JSON数组中进行查找,汇总等等 </br>
     * </br>
     * 不考虑按照数组序号进行查找
     * 
     * @param ja
     * @param pathList
     */
    public abstract void testReadJsonarray(Object ja, List<String> pathList) ;

    /**
     * 从JSON数组中过读取特定的元素,包含简单的过滤
     * 
     * @param ja
     * @param pathList
     */
    public abstract void testFilterJsonarray(Object ja, List<String> pathList);

}

1.2 阿里实现

package study.base.json.fastjson;

import java.util.Arrays;
import java.util.List;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONWriter;

import study.base.classes.ChineseMan;
import study.base.classes.Parent;
import study.base.json.TestJsonPath;

/**
 * JSONPATH练习
 * 
 * @author lzfto
 */
public class TestFastJsonJSONPath  extends TestJsonPath{

    /**
     * 从JSON对象中一次获取多个key
     * 
     * @param jo       JSON对象
     * @param pathList 路径
     */
    @Override
    public void testReadMultiKeyOnce(Object jo, List<String> pathList) {
        for (String path : pathList) {

            try {
                Object item = JSONPath.eval(jo, path);
                if (item != null) {
                    if (item instanceof Parent) {
                        System.out.println(
                                "key[" + path + "]=" + JSON.toJSONString(item, JSONWriter.Feature.PrettyFormat));
                    } else {
                        System.out.println("key[" + path + "]=" + item.toString());
                    }

                } else {
                    System.out.println("key[" + path + "]没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key[" + path + "]没有找到对应的值,因为:"+e.getMessage());
            }
        }

    }

    /**
     * 测试在JSON数组中进行查找,汇总等等 </br>
     * </br>
     * 不考虑按照数组序号进行查找
     * 
     * @param ja
     * @param pathList
     */
    @Override
    public void testReadJsonarray(Object ja, List<String> pathList) {
        for (String path : pathList) {
            try {
                Object item = JSONPath.eval(ja, path);
                if (item != null) {
                    if (item instanceof Parent) {
                        System.out.println(
                                "key[" + path + "]=" + JSON.toJSONString(item, JSONWriter.Feature.PrettyFormat));
                    } else {
                        System.out.println("key[" + path + "]=" + item.toString());
                    }

                } else {
                    System.out.println("key[" + path + "]没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key[" + path + "]没有找到对应的值,因为:" + e.getMessage());
            }
        }
    }

    /**
     * 从JSON数组中过滤掉特定的元素(满足条件的留下)
     * 
     * @param ja
     * @param pathList
     */
    @Override
    public void testFilterJsonarray(Object ja, List<String> pathList) {
        for (String path : pathList) {
            try {
                Object item = JSONPath.eval(ja, path);
                if (item != null) {
                    System.out.println(
                            "key[" + path + "]=" + JSONArray.toJSONString(item, JSONWriter.Feature.PrettyFormat));

                } else {
                    System.out.println("条件[" + path + "]没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key[" + path + "]没有找到对应的值,因为:" + e.getMessage());
            }
        }
    }

    /**
     * @param args
     */
    /**
     * @param args
     */
    public static void main(String[] args) {
        TestFastJsonJSONPath test = new TestFastJsonJSONPath();
        // 1.0 JSONObject中的查找
        Object jo = test.getJObject();
        List<String> list = Arrays.asList(
                // 简单的key查找
                "$.name", "$.age", "$.height", "$.力量", "$.father", "$.mother",
                // 查找层级大于1的子节点
                "$.father.name", "$.mother.name",
                "$.children[*]",  //所有的孩子
                "$.children",  //所有的孩子
                "$.children[0]",  //第一个孩子
                "$.children[0].name",  //第一个孩子的姓名
                // 同时查找多个key/属性,返回的是一个数组,按照顺序的 ["lzf",12,"男"]
                "$[\"name\",\"age\",\"gender\"]",
                "$[name,age]" // 这个是错误的语法
        );
        test.testReadMultiKeyOnce(jo, list);

        // 2.0 JSONArray中的查找
        // 2.1 JSON聚集函数 -- 经过测试 fastjson2的JSONPath并不支持max,min等函数
        // 主要测试:min,max,avg,sum(),concat,keys(),length()
        // 查找成员的成员存在一些问题
        // a.无法同时获取多个数组中的多个key
        // b.不支持基本的四则运算
        // c.不支持特定的函数avg
        // e.无法获取数组成员的指定成员,例如如果某个成员是数组,那么无法从这个数组获取指定序号的成员
        //   例如不能用 [*].children[0] 来或者 每人的第一个孩子
        List<ChineseMan> ja = test.getObjectList();
        //System.out.println(JSONArray.toJSONString(ja, Feature.PrettyFormat));
        list = Arrays.asList("$[0].name", // 第1个元素的name
                  "$[*]" // 返回所有的数组元素
                , "$[*].age.sum()" // 汇总所有的年龄
                , "$[*].age.min()" // 最大年龄
                , "$[*].age.max()" // 最小的年龄
                , "$[*].age.length()" // 数组个数
                , "$[*].length()" // 数组个数
                , "$[*]..power"   // 所有能量
                , "$..power"   // 所有能量
                //返回数组中数组成员的属性,以下几个居然是等价的,这到底是灵活还是阿里程序员的能力导致的?
                , "$[*].children.weight" //返回所有孩子体重,结果是数组*2(因为每人都是2个孩)
                , "$[*].children[0].weight" //返回所有第一个孩子体重,结果是数组*1(因为每人都是2个孩)
                , "$[*].children[*].weight" //返回所有第一个孩子体重,结果是数组*1(因为每人都是2个孩)
                , "$[*].children.weight[0]" //返回所有第一个孩子体重,结果是数组*1(因为每人都是2个孩)
                //如何才能够返回所有人第一个孩子的体重信息?
                
                , "$[*][\"age\"]" // 所有年龄
                , "$[*][\"father\"]" // 所有人员的父亲
                , "$[*][\"father\"].name" // 所有人员的父亲的姓名
                , "$[*].mother.name" // 所有人员的母亲的姓名
                // 以下是错误的语法,或者是不支持的功能
                , "$[*].father.length()+$[*].mather.length()" // 所有人员的父母个数,这个语法不合格。不支持四则运算
                , "$[*][\"father\",\"mother\"]" // 所有人员的父亲母亲 这个语法非法
                , "$[*].age.avg()" // 不支持avg,但可以通过sum/length来解决
        );
        test.testReadJsonarray(ja, list);

        // 3.0 JSONArray的过滤测试
        // fastjson的过滤相当优先,不能使用组合条件
        System.out.println("=========================================================================");
        System.out.println("==============             过滤测试   ===================================");
        System.out.println("=========================================================================");
        list = Arrays.asList("$[*][?(age>90)]" // 返回所有年龄>90的
                , "$[*].children[?(weight<100)]" //返回所有孩子体重小于100的孩子信息(非人员信息)
                , "$[*].children.weight" //返回所有孩子体重
                , "$[*][?(@.children.weight<100)]" //返回所有孩子体重小于100的人员信息(非人员信息)
                , "$[*][?(@.children[*].weight<100)]" //返回所有孩子体重小于100的人员信息(非人员信息)
                , "$[*][?(@.children.weight<100)]" //返回所有孩子体重小于100的人员信息(非人员信息)                
                , "$[*][?(age>60sex='女')]" // 返回所有年龄>60的女性个数
        );
        test.testFilterJsonarray(ja, list);
    }

}

 

1.3jayway实现

package study.base.json.jayway;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.ReadContext;
import com.jayway.jsonpath.internal.filter.FilterCompiler;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;

import study.base.classes.ChineseMan;
import study.base.classes.Parent;
import study.base.json.TestJsonPath;

/**
 * JsonPath练习-基于jayway </br>
 * 据说比fastjson的好用
 * 
 * @author lzfto
 */
public class TestJAYWAYJsonPath extends TestJsonPath {

    /**
     * 从JSON数组中过滤掉特定的元素(满足条件的留下)
     * 
     * @param ja
     * @param pathList
     */
    @Override
    public void testFilterJsonarray(Object ja, List<String> pathList) {
        Predicate[] pa = new Predicate[0];
        ReadContext ctx = JsonPath.parse(ja);
        for (String path : pathList) {
            try {
                Object item = ctx.read(path, pa);
                if (item != null) {
                    System.out.println("key【" + path + "】=" + this.toJsonString(item));

                } else {
                    System.out.println("条件【" + path + "】没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key【" + path + "】没有找到对应的值,因为:" + e.getMessage());
            }
        }
    }

    public void testFilterJsonarray(DocumentContext doc, List<String> pathList,Predicate filter) {
        for (String path : pathList) {
            try {
                Object item = doc.read(path, filter);
                if (item != null) {
                    System.out.println(
                            "key【" + path + "】=" + this.toJsonString(item));
    
                } else {
                    System.out.println("条件【" + path + "】没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key【" + path + "】没有找到对应的值,因为:" + e.getMessage());
            }
        }
    }

    /**
     * 从JSON对象中一次获取多个key
     * 
     * @param jo       JSON对象
     * @param pathList 路径
     */
    @Override
    public void testReadMultiKeyOnce(Object jo, List<String> pathList) {
        Predicate[] pa = new Predicate[0];
        ReadContext ctx = JsonPath.parse(jo);
        for (String path : pathList) {
            try {
                Object item = ctx.read(path, pa);
                if (item != null) {
                    if (item instanceof Parent) {
                        System.out.println("key[" + path + "]=" + this.toJsonString(item));
                    } else {
                        System.out.println("key[" + path + "]=" + item.toString());
                    }

                } else {
                    System.out.println("key[" + path + "]没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key[" + path + "]没有找到对应的值,因为:" + e.getMessage());
            }
        }

    }

    /**
     * 测试在JSON数组中进行查找,汇总等等 </br>
     * </br>
     * 不考虑按照数组序号进行查找
     * 
     * @param ja
     * @param pathList
     */
    @Override
    public void testReadJsonarray(Object ja, List<String> pathList) {
        Predicate[] pa = new Predicate[0];
        ReadContext ctx = JsonPath.parse(ja);
        for (String path : pathList) {
            try {
                Object item = ctx.read(path, pa);
                if (item != null) {
                    if (item instanceof Parent) {
                        System.out.println("key[" + path + "]=" + this.toJsonString(item));
                    } else {
                        System.out.println("key[" + path + "]=" + item.toString());
                    }

                } else {
                    System.out.println("key[" + path + "]没有找到对应的值");
                }
            } catch (Exception e) {
                System.out.println("key[" + path + "]没有找到对应的值,因为:" + e.getMessage());
            }
        }
    }

    /**
     * @param args
     */
    /**
     * @param args
     */
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {

        Configuration.setDefaults(new Configuration.Defaults() {
            private final JsonProvider jsonProvider = new JacksonJsonProvider();
            private final MappingProvider mappingProvider = new JacksonMappingProvider();

            @Override
            public JsonProvider jsonProvider() {
                return jsonProvider;
            }

            @Override
            public MappingProvider mappingProvider() {
                return mappingProvider;
            }

            @Override
            public Set<Option> options() {
                return EnumSet.noneOf(Option.class);
            }
        });
        TestJAYWAYJsonPath test = new TestJAYWAYJsonPath();
        // 1.0 ObjectMapper中的查找
        ChineseMan jo = test.getJObject();
        System.out.println("=========================================================================");
        System.out.println("==============             读取json对象===================================");
        System.out.println("=========================================================================");
        List<String> list = Arrays.asList(
                // 简单的key查找 $ddd.vvvScVSs
                "$", "$.name", "$[?(@.gender=='男')]", "$.*",
                // 查找层级大于1的子节点
                "$.father.name", "$.mother.name", "$.children[*]", // 所有的孩子
                "$.children[0]", // 第一个孩子
                // 同时查找多个key/属性,返回的是一个数组,按照顺序的 ["lzf",12,"男"]
                "$[\"name\",\"age\",\"gender\"]", "$['name','age','gender']");
        Map<String, Object> map = Configuration.defaultConfiguration().mappingProvider().map(jo, Map.class,
                Configuration.defaultConfiguration());
        test.testReadMultiKeyOnce(map, list);

        // 2.0 JSONArray中的查找
        // 2.1 JSON聚集函数 -- 经过测试 fastjson2的JsonPath并不支持max,min等函数
        // 主要测试:min,max,avg,sum(),concat,keys(),length()
        // 查找成员的成员存在一些问题
        // a.如果要访问数组成员的属性,那么不能直接使用.访问,必须使用 [n].prop或者[*].prop,或者使用递归获取..
        // chidlren[*].weight和children..weight在某些情况下是等价的
        System.out.println("=========================================================================");
        System.out.println("==============             读取JSON数组===================================");
        System.out.println("=========================================================================");
        List<ChineseMan> ja = test.getObjectList();
        ja.get(0).getChildren().get(0).setWeight(10);
        ja.get(0).getChildren().get(1).setWeight(20);
        List<Map<String, Object>> listmap = Configuration.defaultConfiguration().mappingProvider().map(ja, List.class,
                Configuration.defaultConfiguration());
        list = Arrays.asList("$[0].name", // 第1个元素的name
                "$[*]" // 返回所有的数组元素
                , "sum($[*].age)" // 汇总所有的年龄
                , "min($[*].age)" // 最大年龄
                , "max($[*].age)" // 最小的年龄
                , "avg($[*].age)" // 平均年龄
                , "$.length()" // 数组个数
                // 返回数组中数组成员的属性,以下几个居然是等价的,这到底是灵活还是阿里程序员的能力导致的?
                , "$[*]..power" // 所有能量
                , "$..power" // 返回所有的power--即所谓的深度扫描deepScan
                , "$[*].children[0].weight" // 返回所有第一个孩子体重,结果是数组*1(因为每人都是2个孩)
                , "$[*].children[*].weight" // 返回所有孩子体重,结果是数组*2(因为每人都是2个孩)
                , "$[*].children..weight" // 返回所有孩子体重,结果是数组*2(因为每人都是2个孩)
                , "$[*][\"father\",\"mother\"]" // 所有人员的父亲母亲--示范:同时读取数组中的多个元素
                , "$[*][\"age\"]" // 所有年龄
                , "$[*][\"father\"]" // 所有人员的父亲
                , "$[*][\"father\"].name" // 所有人员的父亲的姓名
                , "$[*].mother.name" // 所有人员的母亲的姓名
                // 以下是错误的语法,或者是不支持的功能
                , "$[*].father.length()+$[*].mather.length()" // 可以返回 [4,4] 但不是期望的

        );

        test.testReadJsonarray(listmap, list);

        // 3.0 JSONArray的过滤测试
        // a.过滤的语法访问属性通常要带上@.
        // b.可以对过滤后的数据再次过滤(不过这个过滤是直接的属性访问,不是用?语法)
        // c.如果成员也是list,那么无法对检测此类成员的每一个
        // fastjson的过滤相当优先,不能使用组合条件
        System.out.println("=========================================================================");
        System.out.println("==============             过滤测试   ===================================");
        System.out.println("=========================================================================");
        list = Arrays.asList("$[*].age" // 返回所有年龄>90的
                , "$[*][?(@.age>60)].name" // 返回所有年龄>90的姓名
                , "$[*][?(@.age>60)]" // 返回所有年龄>90的
                , "$[*].children" // 返回所有年龄>90的姓名
                , "$[*].children[*].['weight']" // 所有孩子的体重
                , "$[*].children[*].weight" // 所有孩子的体重
                , "$[*].children[*].['name','weight']" // 所有孩子的名称和体重
                , "$[*][?(@.children[*].weight subsetof [10,20])]" // 孩子体重只是10,20的
                , "$[*].children[?(@.weight<100)]['name','weight']" // 返回所有孩子体重小于100的孩子信息(非人员信息)
                , "$[*][?(@.children[0].weight<100)]['name','weight']" // 返回第一个孩子体重小于100的人员信息(非孩子)
        // 以下是错误的语法,或者是不支持的功能
        );
        test.testFilterJsonarray(listmap, list);
        
        //过滤测试
        
        list = Arrays.asList(
                // ------------------------------------------------------------------------
                "$[*][?]" // 返回所有年龄>60的女性个数                
        );
        DocumentContext doc=JsonPath.parse(listmap);
        Filter filter=FilterCompiler.compile("[?(@.age>60)]");
        Filter filter2=FilterCompiler.compile("[?(@.gender=='男')]");
        Filter filter3=filter2.and(filter);
        
        test.testFilterJsonarray(doc, list, filter3);
        
    }

}

二、小结

a.代码完善性-jayway比阿里好。

ali的开源一贯的大毛病:代码质量不怎么样、文档不怎么样、功能也不怎么样。

jayway实现的功能更加完善一些,例如以下一些是阿里没有的:

, "$[*]..power"   // 所有能量
, "$..power"   // 所有能量
, "avg($[*].age)" // 平均年龄    
"$['name','age','gender']"  

总体来说就是jayway的实现会稍微完善一些,友好一些。

b.性能

没有测试过,如果是比较注重效率务必自行测试。如果对效率要求不高的,则推荐jayway

 

总之jsonpath是一个好东西,在不少的业务场景中具有不少的用处。

 

posted @ 2023-12-24 20:53  正在战斗中  阅读(114)  评论(0编辑  收藏  举报