Apache Johnzon 常用 API 与示例详解

详解 Apache Johnzon 2.2.0 三套常用 API——JSON-P(JsonObject/JsonReader/JsonParser/JsonPointer)、JSON-B(Jsonb)与 Mapper,每个分节给出可直接运行的示例代码与真实回显。

Apache JohnzonApache JohnzonJavaApache2026/10/12

Apache Johnzon 常用 API 与示例详解

目标:系统梳理 Apache Johnzon 2.2.0 的三套常用 API——JSON-P 模型与流式 API(jakarta.json.)、JSON-B 标准绑定(jakarta.json.bind.)、Johnzon 自带 Mapper(org.apache.johnzon.mapper.*),每个分节给出可直接运行的示例代码与真实回显。已在 Ubuntu 22.04 + OpenJDK 17.0.2 + Johnzon 2.2.0 实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS,Kernel 6.8.0-138-generic,x86_64
  • OpenJDK 17:17.0.2(build 17.0.2+8-86,/opt/java/jdk-17.0.2)
  • Apache Maven:3.9.9(/opt/maven)
  • Johnzon:2.2.0(johnzon-core / johnzon-mapper / johnzon-jsonb)
  • Jakarta API:jakarta.json-api 2.1.3、jakarta.json.bind-api 3.0.1
  • 示例工程目录:/opt/johnzon-demo(依赖坐标见《Ubuntu 22.04 安装 Apache Johnzon 指南》4.4 节)

二、工具概览

API 族包入口类典型场景
JSON-P 模型 APIjakarta.jsonJson / JsonObject / JsonArray / JsonReader / JsonWriter一次性构建/解析中小 JSON 文档
JSON-P 流式 APIjakarta.json.streamJsonParser / JsonGenerator大文档逐事件处理,内存占用低
JSON-P 指针/补丁jakarta.jsonJsonPointer / JsonPatch结构化修改 JSON
JSON-B 绑定jakarta.json.bindJsonbBuilder / Jsonb / JsonbConfigPOJO ↔ JSON 标准绑定(注解驱动)
Johnzon Mapperorg.apache.johnzon.mapperMapperBuilder / Mapper无需注解的 POJO 映射(反射驱动)
Johnzon 注解org.apache.johnzon.mapper@JohnzonProperty / @JohnzonIgnore / @JohnzonConverterMapper 细粒度控制

运行示例的通用方式(先编译,再按主类执行):

cd /opt/johnzon-demo
mvn -q compile
mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt
java -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -cp target/classes:$(cat cp.txt) demo.JsonpDemo

三、前置条件

  • 已引入 johnzon-core、johnzon-mapper、johnzon-jsonb 与 jakarta 两个 API 依赖(provided,必须显式声明)。
  • 工程已编译:cd /opt/johnzon-demo && mvn compile,依赖 classpath 文件 cp.txt 已生成。
  • 本文示例均为 demo.* 包下的可运行主类,输出为实测回显。

四、API 详解

4.1 JSON-P:构建 JsonObject 与美化输出

Json.createObjectBuilder() 链式构建,JsonWriterFactory 配置 PRETTY_PRINTING 美化输出:

JsonObject obj = Json.createObjectBuilder()
        .add("id", 1001)
        .add("name", "johnzon")
        .add("price", 49.9)
        .add("tags", Json.createArrayBuilder().add("json").add("parser").build())
        .add("author", Json.createObjectBuilder().add("name", "Apache").add("site", "johnzon.apache.org"))
        .build();
System.out.println("[build] compact = " + obj.toString());

JsonWriterFactory wf = Json.createWriterFactory(Map.of(JsonGenerator.PRETTY_PRINTING, true));
StringWriter sw = new StringWriter();
try (JsonWriter w = wf.createWriter(sw)) { w.writeObject(obj); }
System.out.println("[pretty]");
System.out.println(sw.toString());

真实回显:

[build] compact = {"id":1001,"name":"johnzon","price":49.9,"tags":["json","parser"],"author":{"name":"Apache","site":"johnzon.apache.org"}}
[pretty]
{
  "id":1001,
  "name":"johnzon",
  "price":49.9,
  "tags":[
    "json",
    "parser"
  ],
  "author":{
    "name":"Apache",
    "site":"johnzon.apache.org"
  }
}

4.2 JSON-P:JsonReader 解析与取值

String text = "{\"user\":\"root\",\"port\":22,\"roles\":[\"admin\",\"dev\"]}";
try (JsonReader r = Json.createReader(new StringReader(text))) {
    JsonObject parsed = r.readObject();
    System.out.println("[parse] user   = " + parsed.getString("user"));
    System.out.println("[parse] port   = " + parsed.getInt("port"));
    System.out.println("[parse] roles  = " + parsed.getJsonArray("roles"));
    System.out.println("[parse] size   = " + parsed.size());
}

真实回显:

[parse] user   = root
[parse] port   = 22
[parse] roles  = ["admin","dev"]
[parse] size   = 3

4.3 JSON-P:JsonParser 流式解析

事件驱动的拉式解析器,逐事件推进(大文档首选):

try (jakarta.json.stream.JsonParser p = Json.createParser(new StringReader(text))) {
    while (p.hasNext()) {
        jakarta.json.stream.JsonParser.Event e = p.next();
        switch (e) {
            case KEY_NAME -> System.out.println("  KEY_NAME  : " + p.getString());
            case VALUE_STRING -> System.out.println("  VALUE_STRING: " + p.getString());
            case VALUE_NUMBER -> System.out.println("  VALUE_NUMBER: " + p.getString());
            default -> System.out.println("  " + e);
        }
    }
}

真实回显:

[stream]
  START_OBJECT
  KEY_NAME  : user
  VALUE_STRING: root
  KEY_NAME  : port
  VALUE_NUMBER: 22
  KEY_NAME  : roles
  START_ARRAY
  VALUE_STRING: admin
  VALUE_STRING: dev
  END_ARRAY
  END_OBJECT

4.4 JSON-P:JsonPointer 与 JsonPatch 修改

按 RFC 6901 指针路径读取,RFC 6902 补丁原地替换:

System.out.println("[pointer] before price = " + obj.getJsonNumber("price"));
JsonObject after = jakarta.json.Json.createPatchBuilder().replace("/price", jakarta.json.Json.createValue(59.9)).build().apply(obj);
System.out.println("[pointer] after  price = " + after.getJsonNumber("price"));

真实回显:

[pointer] before price = 49.9
[pointer] after  price = 59.9

4.5 JSON-B:POJO 序列化与反序列化

POJO(注解:@JsonbProperty 重命名、@JsonbNillable 保留 null、@JsonbDateFormat 日期格式、@JsonbPropertyOrder 排序):

public static class User {
    public int id;
    @JsonbProperty("username")
    public String name;
    public String role;
    @JsonbNillable
    public String comment;
    @JsonbDateFormat("yyyy-MM-dd HH:mm:ss")
    public LocalDateTime createdAt;
    // 构造与 setter 略
}

Jsonb jsonb = JsonbBuilder.create();
String json = jsonb.toJson(new User(1, "root", "admin", null));
System.out.println("[toJson] " + json);
User back = jsonb.fromJson(json, User.class);
System.out.println("[fromJson] id=" + back.id + " name=" + back.name + " role=" + back.role + " comment=" + back.comment);

真实回显:

[toJson] {"id":1,"role":"admin","createdAt":"2026-10-11 10:30:00","comment":null,"username":"root"}
[fromJson] id=1 name=root role=admin comment=null

4.6 JSON-B:集合绑定与 Map 绑定

泛型集合通过匿名子类保留类型信息;Map 直接绑定:

Type listType = new ArrayList<String>() { }.getClass().getGenericSuperclass();
List<String> tags = jsonb.fromJson("[\"json\",\"bind\",\"johnzon\"]", listType);
System.out.println("[list] " + tags);

Map<String, Object> map = jsonb.fromJson("{\"host\":\"db1\",\"port\":3306}", Map.class);
System.out.println("[map] " + map);

真实回显:

[list] [json, bind, johnzon]
[map] {host=db1, port=3306}

4.7 Mapper:POJO 对象映射与读回

MapperBuilder 零注解构建映射器,writeObjectAsString / readObject 完成往返:

Mapper mapper = new MapperBuilder().build();
Server s = new Server();   // 字段:id=1, host=web1.example.com, port=22, user=root, password=q1w2e3r4
String json = mapper.writeObjectAsString(s);
System.out.println("[write] " + json);

Server back = mapper.readObject(json, Server.class);
System.out.println("[read] host=" + back.getHost() + " port=" + back.getPort() + " user=" + back.getUser());

真实回显:

[write] {"password":"q1w2e3r4","internal":"hidden","port":22,"host":"web1.example.com","id":1,"user":"root"}
[read] host=web1.example.com port=22 user=root

💡 说明:默认 AccessMode 按字段扫描,transient 修饰的 internal 字段也会输出;如需剔除请用 @JohnzonIgnore 注解或字段过滤策略。

4.8 Mapper:集合与泛型类型

数组序列化用 writeArrayAsString(Collection);反序列化用 readObject(String, Type) 配合 JohnzonParameterizedType:

List<Server> servers = Arrays.asList(new Server(), new Server());
String arrayJson = mapper.writeArrayAsString(servers);
System.out.println("[array] " + arrayJson);

List<Server> backList = mapper.readObject(arrayJson,
        new JohnzonParameterizedType(List.class, Server.class));
System.out.println("[array-read] size=" + backList.size() + " first=" + backList.get(0).getHost());

Map<String, Object> m = mapper.readObject("{\"a\":1,\"b\":\"x\"}", Map.class);
System.out.println("[map] " + m);

真实回显:

[array] [{"password":"q1w2e3r4","internal":"hidden","port":22,"host":"web1.example.com","id":1,"user":"root"},{"password":"q1w2e3r4","internal":"hidden","port":22,"host":"web1.example.com","id":1,"user":"root"}]
[array-read] size=2 first=web1.example.com
[map] {a=1, b=x}

4.9 Mapper:注解配置(@JohnzonProperty / @JohnzonIgnore / @JohnzonConverter)

public static class Account {
    @JohnzonProperty("login_name")
    private String name = "root";

    @JohnzonIgnore
    private String password = "q1w2e3r4";

    @JohnzonConverter(SecretConverter.class)   // 自定义转换器:掩码输出
    private String token = "raw-token-abc";
    // getter/setter 略
}

Mapper mapper = new MapperBuilder().build();
System.out.println("[write] " + mapper.writeObjectAsString(new Account()));
Account back = mapper.readObject("{\"login_name\":\"admin\",\"token\":\"x\"}", Account.class);
System.out.println("[read] name=" + back.getName() + " password=" + back.getPassword() + " token=" + back.getToken());

真实回显:

[write] {"login_name":"root","token":"******"}
[read] name=admin password=q1w2e3r4 token=decoded

💡 说明:@JohnzonIgnore 的字段不参与序列化/反序列化,读回时保留字段默认值(q1w2e3r4);转换器在写入时调 toString(掩码)、读取时调 fromString(还原)。

4.10 SPI:Provider 自动发现

Johnzon 通过 META-INF/services 注册(johnzon-core 注册 jakarta.json.spi.JsonProvider → org.apache.johnzon.core.JsonProviderImpl;johnzon-jsonb 注册 jakarta.json.bind.spi.JsonbProvider → org.apache.johnzon.jsonb.JohnzonProvider)。classpath 加入即接管默认实现:

JsonProvider p = JsonProvider.provider();
System.out.println("[JsonProvider.provider] " + p.getClass().getName());
System.out.println("[Json.createObjectBuilder] " + Json.createObjectBuilder().getClass().getName());
JsonProvider explicit = new org.apache.johnzon.core.JsonProviderImpl();
System.out.println("[explicit] " + explicit.getClass().getName());
System.out.println("[JsonbProvider.provider] " + JsonbProvider.provider().getClass().getName());
Jsonb jsonb = JsonbBuilder.create();
System.out.println("[JsonbBuilder.create] " + jsonb.getClass().getName());

真实回显:

[JsonProvider.provider] org.apache.johnzon.core.JsonProviderImpl
[Json.createObjectBuilder] org.apache.johnzon.core.JsonObjectBuilderImpl
[explicit] org.apache.johnzon.core.JsonProviderImpl
[JsonbProvider.provider] org.apache.johnzon.jsonb.JohnzonProvider
[JsonbBuilder.create] org.apache.johnzon.jsonb.JohnzonJsonb

五、注意事项

  • API 选型:无 POJO 的动态 JSON 用 JSON-P;标准 Java EE 栈(Jakarta EE 10+)优先 JSON-B;纯反射零注解映射用 Mapper。三套可共存于同一工程。
  • 资源关闭:JsonReader/JsonWriter/JsonParser/JsonGenerator 均实现 AutoCloseable,务必 try-with-resources。
  • Mapper 与 JSON-B 并存:JSON-B 注解(@JsonbProperty 等)对 Mapper 无效;反之 Johnzon 注解对 JSON-B 亦无效,同一 POJO 混用两套时按各自体系配置。
  • 敏感字段:序列化前用 @JohnzonIgnore/@JohnzonConverter(掩码)处理账号密码类字段(示例账号 root/q1w2e3r4 仅作演示,生产请勿输出明文口令)。
  • 堆内存:示例进程以 -Xms256m -Xmx512m 运行;处理大 JSON 时用流式 API(4.3)并配合 setMaxSize 护栏。

六、常见问题

Q1:JsonProvider.provider() 返回的不是 Johnzon

classpath 中存在其他 JSON-P 实现(如 Glassfish parsson)。用 mvn dependency:tree 排查冲突并排除,或直接 new org.apache.johnzon.core.JsonProviderImpl() 显式实例化。

Q2:Mapper 读 List<POJO> 泛型丢失、返回 LinkedHashMap

直接传 List.class 会丢泛型。用 readObject(json, new JohnzonParameterizedType(List.class, Server.class))(见 4.8)。

Q3:JSON-B 日期序列化成 ISO 带时分秒的默认格式

在字段上加 @JsonbDateFormat("yyyy-MM-dd HH:mm:ss"),或全局 JsonbConfig 设 DATE_FORMAT 属性。

Q4:编译报 no suitable method found for readArray(String, ...)

Mapper.readArray 只接受 Reader/InputStream;字符串场景统一用 readObject(String, Type)(同 4.8 用法),该方法对数组和对象均适用。

Q5:运行中文输出乱码

启动参数加 -Dfile.encoding=UTF-8(或 JAVA_TOOL_OPTIONS 全局设置),Mapper 用 setEncoding("UTF-8")。

七、参考链接