正在浏览 2.0.2 · Viewing 2.0.2|2.0.4 最新2.0.32.0.22.0.1
Skip to content

JSON 模块使用指南 ​

com.alianga.jkit.json 提供完整的 JSON 能力,对外入口集中在 JSON、JSONNode、JSONNodePath 与 JSONSchema 四个类。所有 API 均为零第三方依赖,基于 JDK 标准库实现。

1. 常用对象序列化与反序列化 ​

1.1 序列化 ​

java
import com.alianga.jkit.json.JSON;

Map<String, Object> map = new HashMap<String, Object>();
map.put("msg", "hello, jkit json !");
map.put("name", "zhangsan");

// 普通序列化
String result = JSON.toJsonString(map);
// {"msg":"hello, jkit json !","name":"zhangsan"}

对象、集合、数组、枚举、java.util.Date 以及 java.time 常见类型均可直接序列化。

1.2 反序列化 ​

java
String json = "{\"msg\":\"hello\",\"name\":\"zhangsan\"}";

// 解析为 Map(默认 LinkedHashMap)
Map<String, Object> map = (Map<String, Object>) JSON.parse(json);

// 指定类型解析
Map<String, Object> map2 = JSON.parseObject(json, Map.class);
User user = JSON.parseObject(json, User.class);
List<User> users = JSON.parseArray("[{\"name\":\"A\"}]", User.class);

parseObject / parseArray 用于实体类绑定;parse 用于解析为 Map/List/标量;parseAs 可省去强转。

1.3 读写文件与流 ​

java
JSON.writeJsonTo(obj, new File("/tmp/test.json"));        // 写文件
JSON.writeJsonTo(obj, new FileOutputStream(file));        // 写流
JSON.writeJsonTo(obj, writer);                            // 写 Writer

Map<String, Object> result = JSON.read(file, Map.class);  // 读文件
Map<String, Object> result2 = JSON.read(inputStream, Map.class); // 读流
Map<String, Object> result3 = JSON.read(url, Map.class);  // 读远程 URL

2. 序列化格式化 ​

通过 WriteOption 或 JSONConfig 控制输出格式。

java
// 格式化缩进(默认 tab)
JSON.toJsonString(map, WriteOption.FormatOut);

// 冒号后补空格
JSON.toJsonString(map, WriteOption.FormatOut, WriteOption.FormatOutColonSpace);

// 使用 4 个空格缩进
JSON.toJsonString(map, WriteOption.FormatOut, WriteOption.FormatIndentUseSpace);

// 使用 8 个空格缩进
JSON.toJsonString(map, WriteOption.FormatOut, WriteOption.FormatIndentUseSpace8);

// JSONConfig 精确控制(2 空格缩进)
JSONConfig config = JSONConfig.formatOf();
config.setFormatIndentUseSpace(true);
config.setFormatIndentSpaceNum(2);
JSON.toJsonString(map, config);

常用 WriteOption:

选项说明
FormatOut格式化缩进输出
FormatOutColonSpace冒号后补一个空格
FormatIndentUseSpace / FormatIndentUseSpace8空格缩进
CamelCaseToUnderline驼峰属性转下划线输出
DateAsTime日期序列化为时间戳
WriteEnumAsOrdinal枚举按 ordinal 输出
FullProperty输出 null 属性
IgnoreNullProperty过滤 null 属性(默认)
BytesArrayToHex / BytesArrayToNativebyte[] 输出方式
SkipCircularReference跳过循环引用
NumberAsString大数按字符串输出

3. 驼峰 / 下划线自动转换 ​

java
class User {
    private String userName;
    private String firstName;
    // getter / setter 省略
}

User user = new User();
user.setUserName("tom");
user.setFirstName("Tom");

// 默认输出驼峰:{"userName":"tom","firstName":"Tom"}
JSON.toJsonString(user);

// 转下划线:{"user_name":"tom","first_name":"Tom"}
JSON.toJsonString(user, WriteOption.CamelCaseToUnderline);

反序列化时同时支持驼峰与下划线形式(user_name 与 userName 均可绑定到 userName 字段):

java
User u1 = JSON.parseObject("{\"userName\":\"tom\"}", User.class);
User u2 = JSON.parseObject("{\"user_name\":\"tom\"}", User.class); // 同样生效

4. 实体类解析绑定 ​

java
String json = "{\"name\":\"Tom\",\"age\":18,\"tags\":[\"a\",\"b\"]}";
User user = JSON.parseObject(json, User.class);

// 解析到已有实例(避免反射创建开销)
User target = new User();
JSON.parseToObject(json, target);

// 解析到指定集合
List<User> list = new ArrayList<User>();
JSON.parseToList("[{\"name\":\"A\"}]", list, User.class);

@JsonProperty 注解支持:

java
import com.alianga.jkit.json.annotations.JsonProperty;

class Bean {
    @JsonProperty(name = "alias_name")   // 指定别名
    private String aliasField;

    @JsonProperty(serialize = false)     // 不序列化
    private String skipOnWrite;

    @JsonProperty(deserialize = false)   // 不反序列化
    private String skipOnRead;
}

5. 自定义序列化和反序列化 ​

5.1 类型映射器(推荐) ​

JSONTypeMapper 同时负责序列化与反序列化,注册后全局生效:

java
JSON.register(Point.class, new JSONTypeMapper<Point>() {
    @Override
    public Point readOf(Object value) throws Exception {
        String[] parts = String.valueOf(value).split(",");
        return new Point(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
    }

    @Override
    public JSONValue<?> writeAs(Point value, JSONConfig jsonConfig) throws Exception {
        return value == null ? null : JSONValue.of(value.x + "," + value.y);
    }
});

String out = JSON.toJsonString(new Point(3, 4));     // "3,4"
Point p = JSON.parseObject("\"10,20\"", Point.class);

5.2 字段级映射器 ​

JSONTypeFieldMapper 配合 @JsonProperty(mapper = ...) 只作用于指定字段:

java
class SecretBean {
    @JsonProperty(mapper = ReverseMapper.class)
    private String secret;
}

class ReverseMapper extends JSONTypeFieldMapper<String> {
    @Override
    public String readOf(Object value) throws Exception {
        return new StringBuilder(String.valueOf(value)).reverse().toString();
    }

    @Override
    public JSONValue<?> writeAs(String value, JSONConfig jsonConfig) throws Exception {
        return JSONValue.of(new StringBuilder(value).reverse().toString());
    }
}

5.3 底层序列化器 / 反序列化器 ​

java
JSON.register(MyType.class, new JSONTypeSerializer() { ... });   // 仅序列化
JSON.register(MyType.class, new JSONTypeDeserializer() { ... }); // 仅反序列化
JSON.register(MyType.class, mapper);                             // 二者合一

6. JSONNode:节点树按需解析 ​

JSONNode 适合大文本 JSON 的按需访问:扫描一次、仅解析访问到的路径,无需解析完整 JSON。

java
String json = "{\"name\":\"Tom\",\"dept\":{\"id\":7,\"name\":\"dev\"}}";
JSONNode root = JSONNode.parse(json);

// 通过路径获取值(/ 分隔,数组用下标)
String name = root.getPathValue("/name", String.class);
int id = root.getPathValue("/dept/id", int.class);

// 定位节点
JSONNode dept = root.get("/dept");
JSONNode idNode = root.get("dept/id");

// 直接取值 / 转类型
String s = root.getChildValue("name", String.class);
Map<String, Object> map = root.asMap();
List<Object> list = root.get("/tags").asList();

// 转实体 / 集合
User user = root.toBean(User.class);
List<User> users = root.get("/list").toList(User.class);

6.1 局部提取(from) ​

from 只扫描到目标路径即返回,比 parse 更高效,支持懒加载:

java
JSONNode bookRoot = JSONNode.from(json, "/store/book");
JSONNode title = JSONNode.from(json, "/store/book/0/title");
// 提取并直接转化类型
Double price = JSONNode.from(json, "/store/book/0/price", Double.class);

6.2 节点修改 ​

java
root.setPathValue("/dept/name", "newdev");        // 按路径设值
root.setChildValue("name", "Jerry");              // 设子值
root.setChildValue("extra", "v", true);           // 不存在则创建
root.removeField("dept");                          // 删除字段
root.get("/arr").removeElementAt(0);              // 删除数组元素

6.3 聚合与 diff ​

java
JSONNode arr = JSONNode.parse("[{\"price\":8.95},{\"price\":12.99}]");
arr.max("price");  // 12.99
arr.min("price");  // 8.95
arr.avg("price");  // 平均值

JSONNode[] diff = JSONNode.diff("{\"a\":1}", "{\"a\":2}");

7. JSON xpath 提取 ​

7.1 通配与下标 ​

java
// 提取所有 author
List<JSONNode> authors = JSONNode.extract(json, "/store/book/*/author");

// 指定下标
JSONNode author = JSONNode.extract(json, "/store/book/1/author").get(0);

// 前 2 个(下标 1- 包含 1)
List<JSONNode> top2 = JSONNode.extract(json, "/store/book/1-/author");

// 从下标 1 开始(1+ 包含 1)
List<JSONNode> from1 = JSONNode.extract(json, "/store/book/1+/author");

// 离散下标(编程式构建)
JSONNodePath path = JSONNodePath.create()
        .exact("store").exact("book").indexs(0, 2).exact("author");
List<JSONNode> selected = path.collect(JSONNode.parse(json));

7.2 递归查找(collect) ​

java
// // 递归查找所有 name
List<JSONNode> names = JSONNode.collect(json, "//name");

// 递归 + 下标
List<JSONNode> authors = JSONNode.collect(json, "//book/1+/author");

7.3 属性过滤 ​

过滤表达式以 *[表达式] 形式紧跟路径片段,字符串使用单引号:

java
// author == 'Nigel Rees'
List<JSONNode> matched = JSONNode.collect(json, "/store/book/*[author == 'Nigel Rees']/title");

7.4 首尾与类型转化 ​

java
String first = JSONNode.first(json, "/store/book/*/author", String.class);
String last = JSONNode.last(json, "/store/book/*/author", String.class);
String fallback = JSONNode.firstIfEmpty(json, "/none/*/x", String.class, "default");

8. JSON Schema 校验 ​

java
String schemaJson = "{\n"
        + "  \"type\": \"object\",\n"
        + "  \"properties\": {\n"
        + "    \"name\": {\n"
        + "      \"must\": true,\n"
        + "      \"type\": \"string\",\n"
        + "      \"rules\": [\n"
        + "        {\"expression\": \"value.indexOf('test') > -1\", \"message\": \"必须包含test\"}\n"
        + "      ]\n"
        + "    },\n"
        + "    \"age\": {\"type\": \"number\", \"minimum\": 20, \"maximum\": 100},\n"
        + "    \"key\": {\"type\": [\"number\", \"boolean\"]}\n"
        + "  }\n"
        + "}";

JSONSchema schema = JSONSchema.of(schemaJson);
JSONSchemaResult result = schema.validate("{\"name\":\"test user\",\"age\":33,\"key\":false}");
result.isSuccess();   // true
result.getMessage();  // 失败信息
result.getPath();     // 失败路径

支持的约束关键字:

关键字说明
type类型:object / array / string / number / integer / boolean / null,支持数组多类型
required标准必填写法,字符串数组,写在父级:"required": ["name", "age"]。只要字段存在即满足,值为 null 也算存在
must本库自有必填写法,布尔值,写在字段自己的 schema 里:"name": {"must": true}。要求字段存在且值不为 null
minimum / maximum数值边界
exclusiveMinimum / exclusiveMaximum开区间
minLength / maxLength字符串长度
pattern字符串正则
minItems / maxItems数组长度
items数组元素 schema
enum类型枚举
formaturl / email / date
rules自定义规则,支持 regular 正则与 expression 表达式
anyOf / allOf / oneOf组合校验
disableExtra禁止出现未定义字段

必填:required 与 must 选哪个 ​

两种写法都认,语义不同,混用时都要满足:

java
// 标准写法:父级声明,只要求字段存在
JSONSchema.of("{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\"}}}")
        .validateSuccess("{}");            // false:field 'name' is required but not found

// 自有写法:字段内声明,还要求值不是 null
JSONSchema.of("{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"must\":true}}}")
        .validateSuccess("{\"name\":null}"); // false

接口入参校验推荐用 required(与标准 schema 工具互通),must 适合「不能传 null」的场景。required 列出的字段若还声明了 type,{"v":null} 会因类型不匹配失败——那是 type 的判定,不是必填判定。

9. JSON Patch(RFC 6902) ​

对一份 JSON 文档施加一组操作(补丁),得到修改后的文档。补丁本身是一个 JSON 数组,每个元素是 一个带 op 与 path 字段的操作对象。适用于「只更新大文档里的一小块」「把服务端下发的增量合并到 本地缓存」等场景——不必传整份文档。

支持的操作:

op字段说明
addpath + value在 path 处设值;父是对象则新增/覆盖键,父是数组则按下标插入(- 或下标等于长度时追加到末尾)
removepath删除 path 处的成员;数组按下标删除并前移
replacepath + value替换已存在的成员(path 不存在则报错)
movefrom + path先 remove from,再 add 到 path
copyfrom + path深拷贝 from 处的值再 add 到 path
testpath + value断言 path 处的值与 value 相等,不等则抛出 JSONPatchException

path 是 JSON Pointer(RFC 6901):以 / 分隔的引用记号,空串表示根文档;数组下标是十进制非负整数(0 之外不允许前导零、不允许 +),- 表示数组末尾(仅 add 可用);记号里的 ~1 解码为 /、~0 解码为 ~(用来表达本来含 / 或 ~ 的键名)。 test 对数字按值精确比较(BigDecimal 语义),所以 1 与 1.0 视为相等,而 9007199254740993 与 9007199254740992 不相等——大整数不会被 double 截断成假相等。

java
// 文本入口:自行解析与序列化
String patched = JSONPatch.apply(
        "{\"title\":\"老标题\",\"tags\":[\"a\"]}",
        "[{\"op\":\"replace\",\"path\":\"/title\",\"value\":\"新标题\"},"
      + " {\"op\":\"add\",\"path\":\"/tags/-\",\"value\":\"b\"}]");
// -> {"title":"新标题","tags":["a","b"]}

// 对象入口:对已解析的 Map/List 原地修改,返回同一棵树的引用
Object doc = JSON.parse("{\"a\":1}");
JSONPatch.apply(doc, JSON.parse("[{\"op\":\"add\",\"path\":\"/b\",\"value\":2}]"));

注意:JSONPatch.apply(Object, ...) 会在传入的文档树上原地修改,调用方应使用返回值; copy 会深拷贝源值,避免两边共享同一引用。path 非法、下标越界、操作缺失字段、 删除/替换不存在的成员或 test 不成立时,抛出 JSONPatchException(JSONException 的子类)。

10. 其它能力 ​

java
// NDJSON(JSON Lines)
String nd = JSON.toNdJsonString(Arrays.asList(obj1, obj2));
List parsed = JSON.parseNdJson(nd);

// 排序
String sorted = JSON.sortJsonString("{\"b\":2,\"a\":1}");

// 校验 JSON 合法性
boolean valid = JSON.validate(json);

// 深拷贝
User copy = JSON.cloneObject(user);

// 非标准 JSON 兼容(注释、单引号、无引号 key、尾逗号)
JSON.parse(json, ReadOption.AllowComment,
        ReadOption.AllowSingleQuotes,
        ReadOption.AllowUnquotedFieldNames,
        ReadOption.AllowLastEndComma);

完整测试用例见 jkit-core/src/test/java/com/alianga/jkit/json/JSONTest.java、JSONNodeTest.java 与 JSONSchemaTest.java。

基于 Apache License 2.0 发布