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是一个好东西,在不少的业务场景中具有不少的用处。