项目概述
Snack JSONPath 是一个高性能 Java JSONPath 处理框架,支持:
- JSON DOM 文档模型
- JSONPath 查询(兼容 jayway.jsonpath 和 IETF JSONPath RFC 9535)
- JSONSchema 生成与校验(支持 Draft-07 / Draft-2019-09 / Draft-2020-12)
语言: Java (JDK 8+)
构建: Maven 多模块项目
模块结构
snackjson/
├── snack4/ # 核心 JSON 库
├── snack4-jsonpath/ # JSONPath 实现模块
├── snack4-jsonschema/ # JSONSchema 模块
├── snack4-test/ # 测试模块
└── snack4-test17/ # Java 17+ 专用测试
核心模块详解
1. snack4(核心库)
主要类
| 类 |
职责 |
ONode |
统一节点类型,作为所有 JSON 值的入口类 |
DataType |
数据类型枚举 |
Feature |
配置特性开关枚举 |
Options |
序列化/反序列化配置 |
JsonReader |
JSON 解析器 |
JsonWriter |
JSON 序列化器 |
CodecLib |
编解码器注册表 |
目录结构
snack4/src/main/java/org/noear/snack4/
├── ONode.java # 入口类
├── DataType.java # 数据类型
├── Feature.java # 配置特性
├── Options.java # 配置选项
├── annotation/ # 注解(自定义序列化)
├── codec/ # 编解码系统
│ ├── CodecLib.java # 编解码注册表
│ ├── BeanEncoder.java # Bean 编码器
│ ├── BeanDecoder.java # Bean 解码器
│ ├── decode/ # 解码器实现
│ ├── encode/ # 编码器实现
│ └── create/ # 对象创建实现
├── json/ # JSON 解析/写入
└── jsonpath/ # JSONPath 集成
2. snack4-jsonpath(JSONPath 实现)
目录结构
snack4-jsonpath/
├── JsonPath.java # 主查询引擎
├── JsonPathParser.java # 路径表达式解析器
├── JsonPathProviderImpl.java # SPI 实现
├── JsonPathException.java # 异常类
├── QueryResult.java # 查询结果
├── QueryContext.java # 查询上下文接口
├── QueryContextImpl.java # 查询上下文实现
├── QueryMode.java # 查询模式枚举
├── FunctionLib.java # 函数库(可扩展)
├── Function.java # 函数接口
├── FunctionHolder.java # 函数持有器
├── OperatorLib.java # 操作符库(可扩展)
├── Operator.java # 操作符接口
├── selector/ # 节点选择器
│ ├── Selector.java # 选择器接口
│ ├── NameSelector.java # 按名称
│ ├── IndexSelector.java # 按索引
│ ├── WildcardSelector.java # 通配符
│ ├── SliceSelector.java # 切片 [0:3]
│ ├── FilterSelector.java # 过滤器
│ └── QuerySelector.java # 查询选择
├── segment/ # 路径段
│ ├── Segment.java # 段接口
│ ├── AbstractSegment.java # 抽象基类
│ ├── SelectSegment.java # 基本选择
│ ├── DescendantSegment.java # 递归后代 ..
│ └── FuncSegment.java # 函数段
├── filter/ # 过滤器表达式
│ ├── Expression.java # 表达式(RPN 算法)
│ ├── Term.java # 词项
│ ├── Operand.java # 操作数
│ └── Token.java # 词法标记
├── operator/ # 比较操作符
│ ├── Operator.java # 操作符接口
│ ├── CompareOperator.java # 比较运算
│ ├── MatchesOperator.java # 正则匹配
│ ├── InOperator.java # in
│ ├── NinOperator.java # nin
│ ├── ContainsOperator.java # 包含
│ ├── StartsWithOperator.java # 开头匹配
│ ├── EndsWithOperator.java # 结尾匹配
│ ├── EmptyOperator.java # 空检查
│ ├── SizeOperator.java # 大小检查
│ └── ...
├── function/ # 内置函数
│ ├── LengthFunction.java # length / size
│ ├── CountFunction.java # count
│ ├── MinFunction.java # min
│ ├── MaxFunction.java # max
│ ├── AvgFunction.java # avg
│ ├── SumFunction.java # sum
│ ├── StddevFunction.java # stddev
│ ├── FirstFunction.java # first
│ ├── LastFunction.java # last
│ ├── KeysFunction.java # keys
│ ├── MatchFunction.java # match (RFC)
│ ├── SearchFunction.java # search (RFC)
│ └── ...
└── util/ # 工具类
├── TokenizeUtil.java # 词法分析
├── IndexUtil.java # 索引计算
├── RangeUtil.java # 范围计算
├── RegexUtil.java # 正则匹配
└── ...
路径段 (Segment)
| 类型 |
说明 |
SelectSegment |
基本选择(.key 或 [0]) |
DescendantSegment |
递归后代(..) |
FuncSegment |
函数调用段(@.length()) |
选择器 (Selector)
| 类型 |
语法示例 |
说明 |
NameSelector |
$.store.book |
按属性名选择 |
IndexSelector |
$[0] |
按索引选择数组元素 |
WildcardSelector |
$.* 或 [*] |
通配符选择 |
SliceSelector |
$[1:3] |
切片选择 |
FilterSelector |
[?(@.price<10)] |
过滤器表达式 |
QuerySelector |
[?(@..price>10)] |
嵌套查询 |
内置函数
| 函数 |
说明 |
支持模式 |
length() / size() |
集合长度 |
IETF RFC / Jayway |
count() |
计数 |
IETF RFC |
min() / max() |
最小/最大值 |
Jayway |
avg() / sum() / stddev() |
统计函数 |
Jayway |
keys() |
获取所有键 |
Jayway |
first() / last() |
首/末元素 |
Jayway |
index() |
当前索引 |
Jayway |
match(regex) |
正则匹配 |
IETF RFC |
search(regex) |
正则搜索 |
IETF RFC |
value() |
获取值 |
IETF RFC |
内置操作符
| 分类 |
操作符 |
说明 |
| 比较 |
== / = / != / > / >= / < / <= |
基本比较 |
| 正则 |
=~ |
正则匹配 |
| 集合 |
in / nin |
包含/不包含 |
| 子集 |
subsetof |
子集判断 |
| 逻辑 |
anyof / noneof |
任意/无匹配 |
| 大小 |
size |
集合大小比较 |
| 空值 |
empty |
空值检查 |
| 字符串 |
contains / startsWith / endsWith |
字符串操作 |
查询模式
| 模式 |
说明 |
SELECT |
查询匹配节点(默认) |
CREATE |
创建路径(不存在时创建) |
DELETE |
删除匹配节点 |
3. snack4-jsonschema(JSONSchema)
snack4-jsonschema/
├── JsonSchema.java # 主类
├── JsonSchemaException.java # 异常类
├── SchemaVersion.java # 版本枚举(DRAFT_7/2019-09/2020-12)
├── SchemaKeyword.java # 关键字常量接口
├── SchemaType.java # 类型枚举
├── SchemaFormat.java # 格式常量
├── generate/ # Schema 生成
│ ├── JsonSchemaGenerator.java # 生成器
│ ├── MapperLib.java # 映射注册表
│ ├── SchemaMapper.java # 架构映射接口
│ ├── SchemaPatternMapper.java # 模式映射接口
│ ├── TypeMapper.java # 类型映射接口
│ └── TypePatternMapper.java # 类型模式映射接口
└── validate/ # Schema 校验
├── JsonSchemaValidator.java # 校验器
├── PathTracker.java # 路径跟踪器
├── CompiledRule.java # 编译后规则
└── impl/ # 校验规则实现
├── TypeRule.java # 类型规则
├── RequiredRule.java # 必填规则
├── EnumRule.java # 枚举规则
├── StringConstraintRule.java # 字符串约束
├── NumericConstraintRule.java # 数值约束
├── ArrayConstraintRule.java # 数组约束
├── AdditionalPropertiesRule.java
└── PropertyNamesRule.java
校验规则实现
| 规则类 |
校验内容 |
TypeRule |
类型匹配 |
RequiredRule |
必填字段 |
EnumRule |
枚举值 |
StringConstraintRule |
minLength, maxLength, pattern |
NumericConstraintRule |
minimum, maximum, exclusiveMin/Max |
ArrayConstraintRule |
minItems, maxItems |
AdditionalPropertiesRule |
额外属性控制 |
PropertyNamesRule |
属性名约束 |
架构图
┌─────────────────────────────────────────────────────────────┐
│ ONode │
│ (统一入口类) │
└────────────────────────┬────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────────┐ ┌─────────────────┐
│ JsonReader │ │ JsonWriter │ │ JsonPathProvider │
│ (解析) │ │ (序列化) │ │ (SPI) │
└────────────┘ └──────────────┘ └────────┬─────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ JsonPathParser │ │ JsonPath │ │ FunctionLib │
│ (词法解析) │ │ (查询引擎) │ │ (内置函数) │
│ │ │ │ │ │
│ TokenizeUtil │ │ QueryContext │ │ min/max/avg │
│ ↓ │ │ QueryResult │ │ length/count │
│ List<Token> │ │ QueryMode │ │ match/search │
│ ↓ │ │ │ │ ... │
│ RPN 转换 │ │ select() │ └────────────────┘
└───────┬────────┘ │ create() │
│ │ delete() │
▼ └────────┬─────────┘
┌────────────────┐ │
│ Expression │ │
│ (过滤器表达式) │ │
│ │ │
│ RPN 算法求值 │◀────────────────┤
│ ↑ │ │
│ OperatorLib │ │
└───────┬────────┘ │
│ │
▼ ▼
┌─────────────────────────────┬──────────────────────────────┐
│ Segment │ Selector │
│ (路径段) │ (节点选择器) │
│ │ │
│ SelectSegment ──────────► │ NameSelector ──── 属性名 │
│ DescendantSegment ──────► │ IndexSelector ─── 数组索引 │
│ FuncSegment ────────────► │ WildcardSelector ── 通配符 │
│ │ SliceSelector ───── 切片 │
│ │ FilterSelector ──── 过滤 │
│ │ QuerySelector ───── 查询 │
└─────────────────────────────┴──────────────────────────────┘
核心数据结构
DataType 枚举
enum DataType {
Undefined, // 未初始化
Null, // null 值
Boolean, // true/false
Number, // Integer, Long, Double, BigDecimal
String, // 字符串
Date, // java.util.Date
Array, // List<ONode>
Object // Map<String, ONode>
}
JSONPath 查询执行流程
1. 解析阶段: JsonPath.parse(path)
├── 词法分析: TokenizeUtil.tokenize()
│ └── 字符串 → List<Token>
│
├── 语法解析: JsonPathParser.parse()
│ └── List<Token> → List<Segment>
│
└── 缓存: ConcurrentHashMap 存储已解析路径
2. 执行阶段: JsonPath.select(root)
│
├── QueryContextImpl 初始化
│ └── 设置 QueryMode.SELECT
│
├── 遍历 Segments:
│ └── for each Segment:
│ ├── SelectSegment.resolve(ctx, currentNodes)
│ │ └── 调用 Selector 选择节点
│ ├── DescendantSegment.resolve()
│ │ └── 递归遍历所有后代
│ └── FuncSegment.resolve()
│ └── 调用 FunctionLib 执行函数
│
└── Selector 选择:
├── NameSelector → 按属性名匹配
├── IndexSelector → 按数组索引
├── WildcardSelector → [*] 通配
├── SliceSelector → [1:3] 切片
├── FilterSelector → [?()] 过滤
│ └── Expression.test() 使用 RPN 求值
│ └── OperatorLib 调用操作符
└── QuerySelector → 嵌套查询
3. 结果阶段
└── QueryResult 包装节点列表
├── getNodeList() → List<ONode>
├── asNode() → 第一个节点
└── asString/asInt/asDouble → 类型转换
关键算法
| 算法 |
实现 |
说明 |
| 词法分析 |
TokenizeUtil |
路径表达式 → Token 序列 |
| RPN 求值 |
Expression |
过滤器表达式逆波兰求值 |
| 递归遍历 |
DescendantSegment |
.. 递归后代节点 |
| 路径缓存 |
ConcurrentHashMap |
解析结果缓存复用 |
| 操作符分发 |
OperatorLib |
根据符号查找操作符执行 |
JSONSchema 校验执行流程
1. 编译阶段: JsonSchemaValidator 初始化
└── Schema 解析 → Map<Path, CompiledRule>
└── 规则预编译:类型、约束、条件(allOf 合并)
2. 校验阶段: validator.validate(data)
└── PathTracker 跟踪当前路径
└── 按路径匹配 CompiledRule 执行校验
└── 递归处理 properties / items
└── 处理条件 anyOf / oneOf
3. 异常阶段
└── 校验失败 → 抛出 JsonSchemaException
└── 包含路径和错误信息
Schema 生成流程
1. 入口: schema.generate(Type)
└── JsonSchemaGenerator 构造
2. 生成阶段: doGenerate()
└── 循环引用检测(visited 缓存)
└── 根据类型分发处理:
├── Bean → 生成 properties/required
├── Array → 生成 items
├── Collection → 生成 items(支持泛型)
└── Map → 生成 additionalProperties
3. 定义处理(可选)
└── enableDefinitions=true 时生成 $defs/definitions
└── 支持循环引用 $ref
公开 API
ONode 主入口
// 创建
ONode.ofJson(String json) // 解析 JSON 字符串
ONode.ofBean(Object bean) // 转换 Java Bean
ONode.ofJson(Reader reader) // 从流解析
// DOM 操作
oNode.set("key", value) // 设置属性
oNode.add(value) // 添加数组元素
oNode.get("key") // 获取子节点
oNode.get(0) // 获取数组元素
// JSONPath 查询
oNode.select("$.store.book[0]") // 查询节点
oNode.exists("$.store.book") // 检查存在
oNode.create("$.new.path") // 创建路径
oNode.delete("$.old.path") // 删除节点
// 序列化
oNode.toJson() // 转 JSON 字符串
oNode.toBean(Class<T>) // 转 Java Bean
oNode.toBean(TypeRef<T>) // 带泛型转换
JsonPath 直接访问
// 解析路径(支持缓存)
JsonPath path = JsonPath.parse("$.store.book[*].author");
// 查询
ONode result = path.select(rootNode); // 在 ONode 上查询
ONode result = JsonPath.select(json, "$.path"); // 静态查询
ONode result = JsonPath.select(rootNode, "$.path");
// 检查存在
boolean exists = JsonPath.exists(rootNode, "$.store");
// 创建路径(不存在时创建)
ONode created = JsonPath.create(rootNode, "$.new.path");
// 删除节点
boolean deleted = JsonPath.delete(rootNode, "$.old.path");
// 查询结果处理
QueryResult qr = path.select(rootNode);
qr.getNodeList(); // 获取所有匹配节点
qr.asNode(); // 获取第一个匹配节点
qr.asString(); // 作为字符串
qr.asInt(); // 作为整数
qr.asDouble(); // 作为浮点数
路径语法示例
| 语法 |
说明 |
$ |
根节点 |
@ |
当前节点(过滤器内) |
.name |
子属性 |
['name'] |
子属性(带引号) |
[0] |
数组索引 |
[*] |
数组通配符 |
[1:3] |
数组切片 |
[?(@.price<10)] |
过滤器表达式 |
..name |
递归后代 |
@.length() |
函数调用 |
过滤器表达式
// 基本比较
[?(@.age == 18)]
[?(@.name != "test")]
[?(@.price > 100)]
// 正则匹配
[?(@.email =~ /^.*@.*\.com$/)]
// 集合操作
[?(@.status in ["active","pending"])]
[?(@.role nin ["admin"])]
// 字符串操作
[?(@.name contains "john")]
[?(@.url startsWith "https")]
[?(@.ext endsWith ".json")]
// 逻辑组合
[?(@.age > 18 && @.active == true)]
[?(@.vip == true || @.balance > 1000)]
// 嵌套路径
[?(@.address.city == "Beijing")]
[?(@..price > 100)] // 递归搜索
JSONSchema
// 创建 Schema 实例(支持 Builder 模式)
JsonSchema schema = JsonSchema.builder()
.version(SchemaVersion.DRAFT_7) // 草案版本
.enableDefinitions(true) // 启用定义规范
.printVersion(true) // 打印 $schema
.build();
// 添加自定义映射
schema.addSchemaMapper(User.class, new CustomSchemaMapper());
schema.addTypeMapper(new CustomTypePatternMapper());
// 生成 Schema
ONode schemaNode = schema.generate(User.class);
String schemaJson = schemaNode.toJson();
// 校验数据
JsonSchemaValidator validator = schema.createValidator(User.class);
validator.validate(dataNode); // 抛出 JsonSchemaException 则校验失败
// 也可直接用 JSON 字符串创建校验器
JsonSchemaValidator validator2 = JsonSchema.builder()
.createValidator("{\"type\":\"object\"}");
核心功能
| 功能 |
说明 |
| Schema 生成 |
从 Java Type 生成 JSON Schema |
| Schema 校验 |
校验 ONode 数据是否符合 Schema |
| 版本支持 |
DRAFT_7, DRAFT_2019_09, DRAFT_2020_12 |
| 条件校验 |
支持 allOf, anyOf, oneOf |
| $ref 引用 |
支持内部定义引用 |
| 循环引用 |
自动检测并处理 |
| 自定义映射 |
支持 SchemaMapper/TypeMapper 扩展 |
SchemaKeyword 关键字
| 分类 |
关键字 |
| 元数据 |
$schema, title, description, default, examples |
| 类型 |
type, enum, format, readOnly, writeOnly |
| 对象 |
properties, required, additionalProperties, patternProperties, propertyNames |
| 字符串 |
minLength, maxLength, pattern |
| 数值 |
minimum, maximum, exclusiveMinimum, exclusiveMaximum |
| 数组 |
items, minItems, maxItems |
| 引用 |
$ref, $defs, definitions, $anchor, $comment |
| 条件 |
allOf, anyOf, oneOf |
配置
Options 配置选项
// 创建配置实例
Options opts = Options.of(
Feature.Write_PrettyFormat, // 漂亮格式输出
Feature.Write_UseSmlSnakeStyle // 字段名下划线风格
);
// 或者链式配置
Options opts = new Options(false)
.dateFormat("yyyy-MM-dd") // 日期格式
.writeIndent(" ") // 缩进
.addFeatures(Feature.Write_PrettyFormat)
.addEncoder(MyClass.class, new MyEncoder())
.mapFactory(LinkedHashMap::new)
.listFactory(ArrayList::new);
常用特性 (Feature)
| 特性 |
说明 |
Read_AutoType |
读取时支持 @type 自动类型 |
Read_UseBigDecimalMode |
大数字模式,避免精度丢失 |
Write_PrettyFormat |
漂亮格式(带缩进) |
Write_UseSmlSnakeStyle |
字段名下划线风格 |
Write_UseSmlCamelStyle |
字段名驼峰风格 |
Write_EnumUsingName |
枚举使用名称 |
Write_DateFormat |
日期格式化输出 |
JsonPath_IETF_RFC_9535 |
IETF RFC 9535 兼容模式 |
依赖关系
snack4-parent (父 POM)
├── snack4 (核心)
│ └── 依赖: eggg, slf4j-api
├── snack4-jsonpath
│ └── 依赖: snack4
├── snack4-jsonschema
│ └── 依赖: snack4, slf4j-api