您现在的位置是:首页 > 文章详情

🔥 全球首个支持 IETF JSONPath (RFC 9535) 标准的 Java 框架发布

日期:2025-10-17点击:3

基于jdk8。支持:Json Dom 的构建、编码解转换、获取、JsonPath 查询、JsonSchema 验证。

<dependency>
  <groupId>org.noear</groupId>
  <artifactId>snack4-jsonpath</artifactId>
  <version>4.0.0</version>
</dependency>

Snack-Jsonpath 借鉴了 Javascript 所有变量由 var 申明,及 Xml dom 一切都是 Node 的设计。其下一切数据都以ONode表示,ONode也即 One node 之意,代表任何类型,也可以转换为任何类型。

  • 强调文档树的构建和操控能力
  • 高性能Json path查询(比 jayway.jsonpath 快很多倍)。同时兼容 jayway.jsonpath  IETF JSONPath (RFC 9535) 标准 (用 options 切换)。为下一个十年提供强劲的 JsonPath 体验。
  • 支持 Json schema 架构校验
  • 支持 json5 部分特性(无键字段,注释,等...)
  • 优先使用 无参构造函数 + 字段 编解码(可减少注入而触发动作的风险)

依赖包清单:

依赖包 描述
org.noear:snack4 提供 json dom 构建和编解码支持
org.noear:snack4-jsonpath 提供 json path 查询支持
org.noear:snack4-jsonschema 提供 json schema 校验支持

开源项目仓库地址:

文档资料:

1、版本更新说明

  • 重构整个项目(除了名字没变,其它都变了)
  • 单测覆盖率 98%,历时小半年
  • 支持 IETF JSONPath (RFC 9535) 标准(全球首个支持该标准的 Java 框架)。同时兼容 jayway.jsonpath
  • 添加 json-schema 支持

2、JSONPath 语法参考

语法元素 描述
$ 根节点标识符
@ 当前节点标识符(仅在过滤选择器中有效)
[<selectors>] 子段:选择节点的零个或多个子节点
.name 简写 ['name']
.* 简写 [*]
..[<selectors>] 后代段:选择节点的零个或多个后代
..name 简写 ..['name']
..* 简写 ..[*]
'name' 名称选择器:选择对象的命名子对象
* 通配符选择器:选择节点的所有子节点
3 索引选择器:选择数组的索引子项(从 0 开始)
0💯5 数组切片选择器:数组的 start🔚step
?<logical-expr> 过滤选择器:使用逻辑表达式选择特定的子项
fun(@.foo) 过滤函数:在过滤表达式中调用函数(IETF 标准)
.fun() 聚合函数:作为片段使用(jayway 风格)

过滤选择器语法参考:

语法 描述 优先级
(...) 分组 5
name(...) 函数扩展 5
! 逻辑  4
==,!=,<,<=,>,>= 关系比较符 3
&& 逻辑  2
\|\| 逻辑  1

IETF JSONPath (RFC 9535) 标准定义操作符(支持)

操作符 描述 示例
== 左等于右(注意1不等于'1') $[?(@.a == 1)]
!= 左不等于右 $[?(@.a != 1)]
< 左比右小 $[?(@.a < 1)]
<= 左小于或等于右 $[?(@.a <= 1)]
> 左大于右 $[?(@.a > 1)]
>= 左大于等于右 $[?(@.a >= 1)]

jayway.jsonpath 增量操作符(支持)

操作符 描述 示例
=~ 左匹配正则表达式 [?(@.s =~ /foo.*?/i)]
in 左存在于右 [?(@.s in ['S', 'M'])]
nin 左不存在于右  
subsetof 左是右的子集 [?(@.s subsetof ['S', 'M', 'L'])]
anyof 左与右有一个交点 [?(@.s anyof ['M', 'L'])]
noneof 左与右没有交集 [?(@.s noneof ['M', 'L'])]
size 左(数组或字符串)的大小应该与右匹配 $[?(@.s size @.expected_size)]
empty Left(数组或字符串)应该为空 $[?(@.s empty false)]

IETF JSONPath (RFC 9535) 标准定义函数(支持)

函数 描述 参数类型 结果类型
length(x) 字符串、数组或对象的长度 数值
count(x) 节点列表的大小 节点列表 数值
match(x,y) 正则表达式完全匹配 值,值 逻辑值
search(x,y) 正则表达式子字符串匹配 值,值 逻辑值
value(x) 节点列表中单个节点的值 节点列表

jayway.jsonpath 函数(支持)

函数 描述 输出类型
length() 字符串、数组或对象的长度 Integer
min() 查找当前数值数组中的最小值 Double
max() 查找当前数值数组中的最大值 Double
avg() 计算当前数值数组中的平均值 Double
stddev() 计算当前数值数组中的标准差 Double
sum() 计算当前数值数组中的总和 Double
keys() 计算当前对象的属性键集合 Set<E>
concat(X) 将一个项或集合和当前数组连接成一个新数组 like input
append(X) 将一个项或集合 追加到当前路径的输出数组中 like input
first() 返回当前数组的第一个元素 依赖于数组元素类型
last() 返回当前数组的最后一个元素 依赖于数组元素类型
index(X) 返回当前数组中索引为X的元素。X可以是负数(从末尾开始计算) 依赖于数组元素类型

snack-jsonpath 增量操作符(支持)

操作符 描述 示例
startsWith 左(字符串)开头匹配右 [?(@.s startsWith 'a')]
endsWith 左(字符串)结尾匹配右 [?(@.s endsWith 'b')]
contains 左(数组或字符串)包含匹配右 [?(@.s contains 'c')]

3、JSONPath 语法示例

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": 399
    }
  }
}

示例JSONPath表达式及其应用于示例JSON值时的预期结果

JSONPath 预期结果
$.store.book[*].author 书店里所有书的作者
$..autho 所有作者
$.store.* 商店里的所有东西,包括一些书和一辆红色的自行车
$.store..price 商店里所有东西的价格
$..book[2] 第三本书
$..book[2].author 第三本书的作者
$..book[2].publisher 空结果:第三本书没有“publisher”成员
$..book[-1] 最后一本书
$..book[0,1]
$..book[:2]
前两本书
$..book[?@.isbn] 所有有国际标准书号的书
$..book[?@.price<10] 所有比10便宜的书
$..* 输入值中包含的所有成员值和数组元素

4、放几个应用示例看看

支持 dom 操控

ONode oNode = new ONode();
oNode.set("id", 1);
oNode.getOrNew("layout").then(o -> {
    o.addNew().set("title", "开始").set("type", "start");
    o.addNew().set("title", "结束").set("type", "end");
});

oNode.get("id").getInt();
oNode.get("layout").get(0).get("title").getString();

oNode.getOrNew("list").fillJson("[1,2,3,4,5,6]");

支持 json path 查询、构建、删除

ONode.ofBean(store).select("$..book[?@.tags contains 'war'].first()").toBean(Book.class); //RFC9535 规范,可以没有括号
ONode.ofBean(store).select("$..book[?(!(@.category == 'fiction') && @.price < 40)].first()").toBean(Book.class);
ONode.ofJson(store).select("$.store.book.count()");

ONode.ofBean(store).create("$.store.book[0].category").toJson();

ONode.ofBean(store).delete("$..book[-1]");

支持 json schema 校验

JsonSchema schema = JsonSchema.ofJson("{type:'object',properties:{userId:{type:'string'}}}"); //加载架构定义

schema.validate(ONode.load("{userId:'1'}")); //校验格式

支持序列化、反序列化

User user = new User();
ONode.ofBean(user).toBean(User.class); //可以作为 bean 转换使用
ONode.ofBean(user).toJson();

ONode.ofJson("{}").toBean(User.class);
ONode.ofJson("[{},{}]").toBean((new ArrayList<User>(){}).getClass());

//快捷方式
String json = ONode.serialize(user);
User user = ONode.deserialize(json, User.class);

5、路径树接口

//case1
ONode o = ONode.ofJson(json);
ONode rst = o.select("$.data.list[*].mobile"); //自动为查询到的节点,生成 path 属性
List<String> rstPaths = rst.pathList(); //获取结果节点的路径列表
for(ONode n1 : rst.getArray()) {
    n1.path(); //当前路径
    n1.parent(); //父级节点
}

//case2
ONode o = ONode.ofJson(json).usePaths(); //手动为每个子节点,生成 path 属性
ONode rst = o.get("data").get("list").get(2);
rst.path();
rst.parent();

6、高级定制

Json 编解码定制

Options options = Options.of();
//添加编码器
options.addEncoder(Date.class, (ctx, value, target) -> {
    target.setValue(DateUtil.format(data, "yyyy-MM-dd"));
});
//添加解码器
options.addDecoder(Date.class, ...);
//添加创建器(接管类实例化)
options.addCreator(...);

//添加特性
options.addFeature(Feature.Write_PrettyFormat);

//移除特性
options.removeFeature(Feature.Write_PrettyFormat);

//设置日期格式附
options.addFeature(Feature.Write_UseDateFormat); //使用日期格式
options.dateFormat("yyyy-MM");

//..

String json = ONode.ofBean(orderModel, options).toJson();

JsonPath 函数与操作符定制

import org.noear.snack4.ONode;
import org.noear.snack4.jsonpath.FunctionLib;

public class FunctionDemo {
    public static void main(String[] args) {
        //定制 floor 函数
        FunctionLib.register("floor", (ctx, argNodes) -> {
            ONode arg0 = argNodes.get(0); //节点列表(选择器的结果)

            if (ctx.isDescendant()) {
                for (ONode n1 : arg0.getArray()) {
                    if (n1.isNumber()) {
                        n1.setValue(Math.floor(n1.getDouble()));
                    }
                }

                return arg0;
            } else {
                ONode n1 = arg0.get(0);

                if (n1.isNumber()) {
                    return ctx.newNode(Math.floor(n1.getDouble()));
                } else {
                    return ctx.newNode();
                }
            }
        });

        //检验效果(在 IETF 规范里以子项进行过滤,即 1,2) //out: 1.0
        System.out.println(ONode.ofJson("{'a':1,'b':2}")
                .select("$.a.floor()")
                .toJson());

        //参考 //out: 2.0
        System.out.println(ONode.ofJson("{'a':1,'b':2}")
                .select("$[?floor(@) > 1].first()")
                .toJson());
    }
}
import org.noear.snack4.ONode;
import org.noear.snack4.jsonpath.OperatorLib;

public class OperationDemo {
    public static void main(String[] args) {
        //定制操作符
        OperatorLib.register("startsWith", (ctx, node, term) -> {
            ONode leftNode = term.getLeftNode(ctx, node);

            if (leftNode.isString()) {
                ONode rightNode = term.getRightNode(ctx, node);
                if (rightNode.isNull()) {
                    return false;
                }

                return leftNode.getString().startsWith(rightNode.getString());
            }
            return false;
        });

        //检验效果
        assert ONode.ofJson("{'list':['a','b','c']}")
                .select("$.list[?@ startsWith 'a']")
                .size() == 1;
    }
}

 

原文链接:https://www.oschina.net/news/377931
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章