Snack JSONPath 项目架构分析

项目概述

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 枚举

java 复制代码
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 主入口

java 复制代码
// 创建
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 直接访问

java 复制代码
// 解析路径(支持缓存)
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

java 复制代码
// 创建 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 配置选项

java 复制代码
// 创建配置实例
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
相关推荐
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】 原型模式
java·开发语言·后端·设计模式·原型模式
试试勇气2 小时前
C++实现json-rpc框架
网络协议·rpc·json
Lyyaoo.2 小时前
【JAVA基础面经】线程的状态
java·开发语言
Hello小赵2 小时前
C语言如何自定义链接库——编译与调用
android·java·c语言
希望永不加班2 小时前
SpringBoot 配置绑定:@ConfigurationProperties
java·spring boot·后端·spring
悟空码字2 小时前
MySQL性能优化的天花板:10条你必须掌握的顶级SQL分析技巧
java·后端·mysql
indexsunny2 小时前
互联网大厂Java面试实战:Spring Boot、MyBatis与Kafka在电商场景中的应用
java·spring boot·面试·kafka·mybatis·电商·技术栈
殷紫川2 小时前
CompletableFuture 异步编程全解:核心能力、编排方案、异常处理与超时控制
java
小贾要学习3 小时前
【Linux】应用层自定义协议与序列化
linux·服务器·c++·json