对象映射
线程安全、复用与代码生成
ForyJson 在调用 build() 后不可变且线程安全。应复用同一个实例,而不是为每次操作都创建
builder 和 ForyJson 实例。已注册及通过注解选定的 JsonValueCodec 实例和 JsonTypeChecker 可能被
并发调用,因此也必须是线程安全的。
默认启用代码生成和异步编译。在诊断问题或运行环境禁止运行时编译时,可以禁用代码生成:
ForyJson json =
ForyJson.builder()
.withCodegen(false)
.withAsyncCompilation(false)
.build();
withConcurrencyLevel 设置可并发执行的根操作数量上限。其他调用方需要等待,直至某个固定的
执行状态可用。同一个 ForyJson 实例上的根 API 不可重入:自定义编解码器必须继续使用传入的
具体 reader 或 writer,而不能在该实例上调用 toJson、toJsonBytes、writeJsonTo 或
fromJson。
Java 对象映射
默认属性发现
默认情况下,Fory JSON 会把 Java 属性名相同的成员合并为一个逻辑属性:
- 类层次结构中符合条件的实例字段,包括 private、protected、包私有和 public 字段;
- 名为
getX()的 public 非 static JavaBean getter; - 名为
isX()的 public 非 static boolean getter; - 名为
setX(value)的 public 非 static void setter。
static、transient、synthetic 和 Class<?> 字段会被排除。getClass() 以及值类型为 Class<?>
的访问器也会被排除。在不符合条件的成员上放置注解会直接报错,而不是被静默忽略。
读取 JSON 时会恢复符合条件的实例字段,包括普通 final 字段。子类字段隐藏父类同名字段时,
读写两个方向均以子类字段作为 JSON 属性。忽略该字段不会暴露被隐藏的父类字段。
普通对象属性在读取时使用声明类型。通过具体的声明基类类型写入子类实例时,Fory 会写出子类的
映射属性。通过基类类型读取相同 JSON 时,会创建基类实例并忽略子类独有的属性。
读取器需要恢复子类型时,应使用 @JsonSubTypes 或自定义编解码器。
字段模式
字段模式会停用 getter 和 setter 发现,但仍保留符合条件的字段:
ForyJson json = ForyJson.builder().withFieldMode(true).build();
字段模式下,方法不属于 JSON 属性模型,因此在方法上使用注解是无效的。
构造与输入行为
Fory JSON 支持普通具体类、Java record,以及显式声明 JsonCreator 构造函数或工厂方法的类。
- record 使用其规范构造函数。
- 基于 creator 的类仅使用声明的 creator 读取 Schema,之后不会调用 setter。
- 未知的对象成员会被跳过。
- 带无参构造函数的普通类会先运行该构造函数,再为可读属性赋值。因此,缺失属性会保留字段 初始化器或该构造函数设定的值。
- 在普通 JVM 上,不带无参构造函数的类会在不运行构造函数或字段初始化器的情况下完成分配。 其缺失属性会保留 JVM 的零值或 null 值。
- creator 的引用类型参数默认为 null,基本类型参数默认为零。
- 重复的普通属性以最后一个值为准。多态鉴别字段的要求更严格,必须且只能出现一次。
- 基本类型目标不接受 JSON null。大多数引用类型目标会返回 null,但选定的内置或自定义编解码器
可以定义不同的结果;例如,声明为
Optional的目标会返回Optional.empty()。
Android 无法构造不具备可用无参构造函数的普通类。JDK 25 及更高版本上的 GraalVM native image
对大多数普通类也有这一要求;唯一受支持的例外是:某个 Serializable 类的第一个不可序列化
超类为 Object。如需可移植的构造约定,请使用 record、JsonCreator 或无参构造函数。不要把
普通构造函数的副作用用作反序列化完成钩子:即使无参构造函数会运行,属性赋值也发生在其后;
而绕过构造函数的路径根本不会运行它。
必需的构造函数属性
启用 failOnMissingRequiredProperties(true),可以拒绝缺少未声明默认值的普通构造函数或工厂属性的输入:
ForyJson json = ForyJson.builder()
.failOnMissingRequiredProperties(true)
.build();
此选项默认为 false,适用于 record、基于属性的 JsonCreator 模型、Scala case class 和 Kotlin
构造函数模型,包括嵌套对象。声明的语言默认值及现有的 Optional、集合、Map 和数组默认值仍可使用。
未声明默认值的普通属性必须出现在输入中,不再自动填充零、false 或 null。此选项不会引入新的容器默认值:
没有默认值的 Kotlin 非空集合仍为必需属性,而 Scala 集合保留现有的空集合默认值。
使用默认的 onNullRead(SET) 时,显式 JSON null 视为已提供的属性值,并遵循属性的类型和可空性规则。
使用 SKIP 时,该次输入被省略,不能满足必需参数要求。被忽略的属性、普通无参 Java Bean
以及构造后赋值的属性保持原有行为。完整的自定义对象编解码器自行负责缺失字段规则。
此选项不改变写入。如果包含策略省略了必需属性,生成的 JSON 可能被严格读取器拒绝。应在输出中保留该属性, 或使用未启用此选项的读取器。
Kotlin 对象映射
为 Kotlin/JVM 类安装 fory-json-kotlin 并使用 ForyJsonKotlin.builder()。Kotlin 普通类和 data class 使用选定的构造函数、精确属性类型、编译器默认值和声明的可空性,不使用 Java 绕过构造函数的回退路径。成员缺失或其 null 输入被跳过时使用默认值。默认情况下,显式 JSON null 是已提供的值,非空参数会拒绝它。
泛型、可空、无符号和值类根类型应使用 jsonTypeRef<T>()。标准数组、集合和 Map 继续使用常规 Fory JSON 表示。完整语言类型表、单例/值类行为和省略规则见 Kotlin 指南。
读取端 null 处理
使用独立的 builder 设置可保留已有属性值,并省略 null 容器项:
import org.apache.fory.json.annotation.JsonProperty.NullHandling;
ForyJson json = ForyJson.builder()
.onNullRead(NullHandling.SKIP)
.onContentNullRead(NullHandling.SKIP)
.build();
两者均默认为 SET。FAIL 拒绝显式 null;SKIP 省略该次输入。
builder 只接受具体模式,不接受 DEFAULT 或 Java null。每个构建的实例保留自己的配置。
使用 @JsonProperty(onNullRead = SET) 或 onContentNullRead = SET 可让单个属性不受对应全局规则影响。
内容设置覆盖直接的集合或数组元素以及 Map 值,包括根容器和动态 JSON 树。
属性设置作用于带类型的对象属性,包括嵌套对象,但不改变根 null 值。
见注解语义与示例。
支持的 Java 类型
下列类型组具有内置映射。具体编码表示形式都是稳定的 JSON 值,但在精度或构造方式很重要时,应用 Schema 仍应声明预期的 Java 类型。
| 类型组 | 支持的类型与行为 |
|---|---|
| 核心标量 | boolean、数值基本类型、char、对应的装箱类型、String、CharSequence、StringBuilder、StringBuffer |
| 数值 | Number、BigInteger、BigDecimal、Fory Float16 和 BFloat16、AtomicInteger、AtomicLong |
| 枚举 | 枚举常量名表示为 JSON 字符串 |
| 数组 | 基本类型数组、装箱类型数组、String 数组、对象数组和多维数组 |
| 集合 | Collection、List、Set、Queue、deque、blocking、sorted 和 navigable 接口;对应的抽象基类;EnumSet;以及具有可访问无参构造函数的具体实现 |
| Map | Map、sorted、navigable 和 concurrent 接口;AbstractMap;EnumMap;以及具有可访问无参构造函数的具体实现 |
| Optional 与原子类 | Optional、OptionalInt、OptionalLong、OptionalDouble、AtomicBoolean、AtomicReference 和原子数组 |
| 时间 | Date、Calendar、TimeZone、LocalDate、LocalTime、LocalDateTime、Instant、Duration、ZoneOffset、ZoneId、ZonedDateTime、Year、YearMonth、MonthDay、Period、OffsetTime、OffsetDateTime 以及受支持的 chronology 日期类型 |
| 其他 JDK 类型 | UUID、URI、File、Path、Locale、Charset、Currency、Pattern、BitSet、ByteBuffer |
| 可选模块 | java.sql.Date、Time 和 Timestamp;存在 Guava 时还支持 Guava ImmutableList、ImmutableSet、ImmutableSortedSet、ImmutableMap、ImmutableBiMap、ImmutableSortedMap 和 ImmutableIntArray |
| 对象 | 可变具体类、record、基于 creator 的类、JsonObject 和 JsonArray |
集合接口会根据声明的接口使用标准可变实现进行重建,例如 ArrayList、LinkedHashSet、
ArrayDeque、LinkedBlockingQueue、LinkedBlockingDeque 或 TreeSet。Map 接口同样会使用
LinkedHashMap、TreeMap、ConcurrentHashMap 或 ConcurrentSkipListMap。无法重建
ArrayBlockingQueue、Arrays.asList 的结果、JDK 不可变集合、空集合/单例集合/不可修改包装器、
受构造函数约束的实现,以及未列出的 Guava 不可变实现。Guava 支持是可选的,并不会使 Guava
成为必需的应用依赖。
非有限 float 和 double 值使用带引号的字符串 "NaN"、"Infinity" 和 "-Infinity"。
需要保留任意精度时,请使用显式的 BigInteger 或 BigDecimal 目标类型。
声明为布尔和数值的目标也接受 JSON 字符串中的常规 token 文本,例如 "true"、"42" 或 "123.45",无需 builder 选项。此行为适用于根值、对象成员、数组、集合和 Map。Fory 写入时仍输出原生 JSON 布尔与数字 token。Object 目标保留自然 JSON 类型,因此带引号的值仍是 String;以 Number 读取带引号的数值时使用 Double。
内置表示形式
这些内置值使用以下常规 JSON 形式:
| Java 类型 | JSON 表示形式 |
|---|---|
| 布尔和数值标量 | 写入原生 JSON 布尔或数字;声明类型的目标也可读取带引号的相同 token 文本 |
| Enum | 常量名称字符串 |
Date、Calendar、java.sql.Date、Time、Timestamp | 表示 epoch 毫秒数的数值 |
TimeZone | 时区 ID 字符串 |
| Java 时间类型以及受支持的 chronology 日期类型 | 对应的标准文本形式字符串 |
UUID、URI、File、Path、Locale、Charset、Currency、Pattern | 对应类型的文本字符串;File 和 Path 使用路径文本,Locale 使用语言标签,Pattern 不保留 flags |
BitSet | 由有符号 long 字组成的数组,来源为 BitSet.toLongArray() |
ByteBuffer | 从 position 到 limit 的剩余范围所对应的有符号字节值数组 |
| Optional 和原子包装类型 | 直接使用其中包含的标量、数组或值 |
Calendar 会把 epoch 毫秒数读入新的 GregorianCalendar;不会保留原始日历子类型、时区和其他配置。
null Optional 引用和空 Optional 都会写为 JSON null;将 JSON null 读取为声明的 Optional 类型时,
会得到相应的空 Optional。
动态 JSON 树
以 Object 类型读取时,会使用自然的 JSON 值:
| JSON 值 | Java 值 |
|---|---|
| 对象 | JsonObject |
| 数组 | JsonArray |
| 字符串 | String |
| 布尔值 | Boolean |
long 范围内的整数 | Long |
| 更大的整数 | BigInteger |
| 小数或带指数形式的数值 | Double |
| Null | null |
JsonObject 会保留成员插入顺序,JsonArray 可变。也可以直接创建并写出它们。
import org.apache.fory.json.JsonArray;
import org.apache.fory.json.JsonObject;
JsonObject object = new JsonObject();
JsonArray items = new JsonArray();
items.add(1);
items.add("two");
object.put("items", items);
String encoded = json.toJson(object);
Map 键
JSON 对象成员名都是字符串。声明的 Map 键支持 String、byte、short、int、long、
对应的装箱类型和枚举。声明使用 Object 键的 Map 可以写出 String、数值、boolean、字符和
枚举键,但读取时都会得到字符串,因为 JSON 不保留原始键类型。不接受 null Map 键。
Builder 配置
如需默认省略空的对象属性:
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonProperty.Include;
ForyJson json = ForyJson.builder().defaultPropertyInclusion(Include.NON_EMPTY).build();
defaultPropertyInclusion 和 writeNullFields 修改同一个设置,以最后一次调用为准。
builder 接受 ALWAYS、NON_NULL 和 NON_EMPTY,拒绝 DEFAULT 和 NON_DEFAULT。
省略默认值需要通过属性上的 @JsonProperty(include = NON_DEFAULT) 或类上的 @JsonInclude(NON_DEFAULT)
显式授权;支持的默认值来源、构造与求值影响以及错误行为见默认值省略。
两种授权形式都会保留没有默认值的属性,包括其值为 null 或零时。
属性上的 JsonProperty.include 优先于类上的 JsonInclude,后者优先于 builder 默认值。空值定义与适用范围见
属性包含策略。
| Builder 方法 | 默认值 | 用户可见的效果 |
|---|---|---|
defaultPropertyInclusion(Include) | NON_NULL | 对象属性的默认包含策略 |
writeNullFields(boolean) | false | 为 true 时选择 ALWAYS,为 false 时选择 NON_NULL |
writeLongAsString(boolean) | false | 将内置 64 位整数值写为十进制字符串 |
escapeNonAscii(boolean) | false | 将生成的 JSON 字符串和名称中的非 ASCII 字符转义 |
byteArrayFormat(JsonByteArray.Format) | BASE64 | 字节数组读写的默认表示方式 |
withCodegen(boolean) | true | 启用生成的对象编解码器 |
withAsyncCompilation(boolean) | true | 异步编译生成的编解码器 |
onNullRead(NullHandling) | SET | 处理显式 null 属性值 |
onContentNullRead(NullHandling) | SET | 处理数组或集合中的 null 元素以及 Map 中的 null 值 |
withFieldMode(boolean) | false | 为 true 时,仅发现字段而不使用 getter/setter |
withPropertyNamingStrategy(strategy) | LOWER_CAMEL_CASE | 为未显式指定 JsonProperty 名称的属性命名 |
withMaxCachedFieldNames(int) | DEFAULT_MAX_CACHED_FIELD_NAMES (8192) | 每个 reader 的字段名缓存条目数;零表示禁用缓存 |
withConcurrencyLevel(int) | max(1, 2 * processors) | 根操作的最大并发数 |
withBufferSizeLimitBytes(int) | 2 MiB | 可保留的输出缓冲区或字符串解码缓冲区的最大容量 |
registerCodec(type, codec) | None | 替换允许注册的精确类的完整 JSON 编解码器 |
registerMixin(mixinType) | None | 将一个注解 Mixin 应用于其精确声明的目标 |
使用 byteArrayFormat 为普通 byte[] 根值、未标注的属性及容器中的值选择统一表示,
包括嵌套数组、集合、Map 和 Optional:
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonByteArray;
ForyJson hexJson = ForyJson.builder().byteArrayFormat(JsonByteArray.Format.BASE16).build();
String text = hexJson.toJson(new byte[] {1, -2, 3}); // "\"01fe03\""
byte[] bytes = hexJson.fromJson(text, byte[].class);
默认的 BASE64 写入带填充符的标准 Base64 字符串。BASE16 写入不带前缀或分隔符的小写十六进制
字符串,读取时接受大小写字母和 JSON 字符串转义,拒绝奇数长度和非十六进制字符。
ARRAY 使用 [-128, 127] 范围内的有符号数字。空数组在字符串格式下写为 "",在 ARRAY 下写为 []。
三种格式都接受 JSON null。读取使用配置的表示方式,不猜测其他格式。
属性上的 JsonByteArray 注解(包括 Mixin 提供的注解)覆盖全局默认值。
声明位置上的自定义编解码器保留其完整表示;Kotlin 无符号语义数组仍使用数字数组。
当 64 位整数必须经过 JavaScript 且不能损失 Number 精度时,请启用 writeLongAsString(true)。
该设置会将内置 long/Long、AtomicLong、AtomicLongArray 和 OptionalLong 值写为带引号的
十进制字符串,也会沿着数组、collection、Map 值、Optional<Long>、AtomicReference<Long> 和
等效语言模块容器中的声明 Long 子项生效。无论是否启用该设置,Reader 都同时接受数字和带引号的
整数 token。自定义编解码器以及声明位置上的编解码器或格式注解仍保持自己的输出形式。
并发级别和缓冲区保留限制必须为正数。字段名缓存上限分别应用于每个 reader;零会禁用该缓存。该上限 只限制缓存的字段名数量,不限制输入中可接受的名称。缓冲区保留设置不会限制 JSON 输入或输出大小, 只限制一次操作后各个可复用输出缓冲区或字符串解码缓冲区的容量,不限制实例使用的总内存。
有关类加载、类型策略、嵌套深度、对象图内存限制和外部输入控制,请参阅 Fory JSON 安全。
调用 build() 之后再修改 builder,不会改变已有的 ForyJson 实例。
在 Android 上禁用运行时代码生成和异步编译。GraalVM 原生镜像中无法进行运行时编译。Fory JSON 会为默认配置以及每个可达 ForyJsonProvider 配置下的可达模型生成编解码器。没有匹配生成编解码器的模型使用解释执行编解码器。其他 builder 选项保持上述行为。
非 ASCII 转义
当接收方要求使用 JSON Unicode 转义表示非 ASCII 字符时,可以启用:
import org.apache.fory.json.ForyJson;
ForyJson json = ForyJson.builder().escapeNonAscii(true).build();
String text = json.toJson("汉é"); // "\u6c49\u00e9"
byte[] utf8 = json.toJsonBytes("汉é");
该选项适用于所有 String、UTF-8、流式和格式化写入 API 中的字符串和字符值、属性名、Map 键、
枚举名和子类型名。U+007F 以上的字符使用小写十六进制转义,补充字符使用两个 UTF-16 代理项转义。
JSON 必需的转义保持不变,字符串中的无效代理项序列仍会失败。
<、> 和 & 等 ASCII 字符不会被额外转义。
该设置默认为 false,实例构建后固定。需要交替使用不同输出策略时,应复用两个实例。
格式化输出仍按次调用选择,读取行为不受影响。关闭此选项会保留既有输出表示,包括原有的转义。
调用方提供的原始 JSON(包括 @JsonRawValue)会原样保留,因此包含原始 JSON 的文档仍可能含有
非 ASCII 字符。自定义编解码器应使用结构化字符串写入方法以遵循该选项;直接写入原始内容的转义
仍由编解码器作者负责。
格式化输出
可以为单次序列化调用选择便于阅读的输出:
ForyJson json = ForyJson.builder().build();
String text = json.toPrettyJson(value);
byte[] utf8 = json.toPrettyJsonBytes(value);
String compact = json.toJson(value);
格式与为对象和数组都配置两个空格缩进的 Jackson pretty printer 一致。每层容器增加一级缩进,
冒号两侧各有一个空格。空对象和空数组分别为 { } 和 [ ],换行使用 \n,末尾没有换行。
两个 API 均保留字符串的逻辑值,并遵循 escapeNonAscii。原始 JSON 值及其空白保持原样。
读取同时接受紧凑和格式化 JSON。同一个实例可以按次切换输出格式;既有的
toJson、toJsonBytes 和 writeJsonTo 继续输出紧凑 JSON。