This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
The following commit(s) were added to refs/heads/main by this push:
new 60e27dd6e2 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
60e27dd6e2 is described below
commit 60e27dd6e2b0181dd983da4a4fe345986fc4ad56
Author: chaokunyang <[email protected]>
AuthorDate: Tue Jul 14 16:10:53 2026 +0000
🔄 synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/java/json-support.md | 267 +++++++++++++++++++++++++++++++++++++---
1 file changed, 249 insertions(+), 18 deletions(-)
diff --git a/docs/guide/java/json-support.md b/docs/guide/java/json-support.md
index c41c7f9ee1..343ed46e8f 100644
--- a/docs/guide/java/json-support.md
+++ b/docs/guide/java/json-support.md
@@ -20,8 +20,9 @@ license: |
---
Fory JSON is Apache Fory's thread-safe Java JSON codec. It supports Java
objects, records,
-immutable creator-based classes, common JDK types, generic containers, exact
custom codecs, and
-finite annotation-declared polymorphism through interpreted and
runtime-generated codecs.
+immutable creator-based classes, common JDK types, generic containers, custom
complete-value
+codecs, and finite annotation-declared polymorphism through interpreted and
runtime-generated
+codecs.
Fory JSON is separate from Fory's binary native and xlang protocols. Use JSON
for interoperable
text payloads such as HTTP APIs, browser traffic, logs, and configuration. Use
the binary protocol
@@ -168,8 +169,8 @@ schema.
## Thread safety and code generation
-`ForyJson` is immutable and thread-safe after `build()`. Registered codecs and
type checkers are
-shared and must also be thread-safe.
+`ForyJson` is immutable and thread-safe after `build()`. Registered and
annotation-selected
+`JsonValueCodec` instances and type checkers are shared and must also be
thread-safe.
Code generation and asynchronous compilation are enabled by default. Disable
them for diagnostics
or environments that prohibit runtime compilation:
@@ -301,7 +302,7 @@ output size. Builder changes after `build()` do not mutate
an existing runtime.
## Annotations
Fory JSON provides `JsonProperty`, `JsonPropertyOrder`, `JsonIgnore`,
`JsonAnyProperty`,
-`JsonAnyGetter`, `JsonAnySetter`, `JsonCreator`, and `JsonSubTypes` under
+`JsonAnyGetter`, `JsonAnySetter`, `JsonCreator`, `JsonCodec`, and
`JsonSubTypes` under
`org.apache.fory.json.annotation`. They are not Jackson, Gson, or Fory
binary-protocol annotations.
```java
@@ -311,6 +312,7 @@ import org.apache.fory.json.PropertyNamingStrategy;
import org.apache.fory.json.annotation.JsonAnyGetter;
import org.apache.fory.json.annotation.JsonAnyProperty;
import org.apache.fory.json.annotation.JsonAnySetter;
+import org.apache.fory.json.annotation.JsonCodec;
import org.apache.fory.json.annotation.JsonCreator;
import org.apache.fory.json.annotation.JsonIgnore;
import org.apache.fory.json.annotation.JsonProperty;
@@ -347,7 +349,7 @@ setter-only, creator-only, or write-ignored property is
invalid.
Inclusion affects writing only. Identical repeated declarations are allowed;
conflicting explicit
names, indexes, or non-default policies fail. Two properties cannot normalize
to the same JSON
name. `JsonProperty` cannot be combined with an Any logical property or
declared on a
-`JsonAnySetter`. `NON_EMPTY`, aliases, formatting, and annotation-selected
codecs are unsupported.
+`JsonAnySetter`. `NON_EMPTY`, aliases, and formatting are unsupported.
### `JsonPropertyOrder`
@@ -623,25 +625,30 @@ public interface Shape {}
Wrapper inclusions support exact custom subtype codecs. Logical names and
resolved concrete,
assignable classes must each be unique. Only exact listed runtime classes are
accepted; listing a
parent does not admit descendants. The annotation is read from the declared
base itself and is not
-inherited from another annotated base. Null is plain JSON null unless a custom
codec on the
-declared base replaces the annotation. Readers accept only the configured
shape, so changing
-inclusion is a wire-format change. At GraalVM native-image runtime, use class
literals instead of
-`className`.
+inherited from another annotated base. Null is plain JSON null unless codec
precedence selects a
+custom complete-value codec for the declared base, replacing the annotation.
Readers accept only
+the configured shape, so changing inclusion is a wire-format change. At
GraalVM native-image
+runtime, use class literals instead of `className`.
## Custom codecs
-An exact custom codec replaces the complete mapping for its registered class
and owns null:
+`JsonValueCodec<T>` reads and writes one complete JSON value, including null,
through Fory's
+concrete String/UTF-8 writers and Latin-1/UTF-16/UTF-8 readers. It is a direct
streaming-value SPI,
+not a JSON abstract syntax tree (AST) codec. It never handles Map keys;
`MapKeyCodec` owns JSON
+object member names.
+
+Implement all five representations with the same JSON shape:
```java
import java.math.BigDecimal;
-import org.apache.fory.json.codec.JsonCodec;
+import org.apache.fory.json.codec.JsonValueCodec;
import org.apache.fory.json.reader.Latin1JsonReader;
import org.apache.fory.json.reader.Utf16JsonReader;
import org.apache.fory.json.reader.Utf8JsonReader;
import org.apache.fory.json.writer.StringJsonWriter;
import org.apache.fory.json.writer.Utf8JsonWriter;
-public final class MoneyCodec implements JsonCodec<Money> {
+public final class MoneyCodec implements JsonValueCodec<Money> {
@Override
public void writeString(StringJsonWriter writer, Money value) {
if (value == null) {
@@ -696,10 +703,232 @@ ForyJson json =
.build();
```
-The parent property still controls its name, ignore direction, and inclusion
before the codec runs.
-The codec instance is shared concurrently and must be thread-safe. A custom
codec on a subtype is
-compatible with wrapper inclusion, not inline property inclusion. A codec on
the base replaces its
-`JsonSubTypes` annotation.
+The parent property still controls its name, ignore direction, and null
inclusion before the codec
+runs. An emitted property and every array, collection, map-value, Optional, or
atomic-reference
+position delegate the complete value, including null. The registered codec
instance is shared
+concurrently and must be thread-safe. A custom codec on a subtype is
compatible with wrapper
+inclusion, not inline property inclusion. A codec on the base replaces its
`JsonSubTypes`
+annotation.
+
+### `JsonCodec` declarations and type uses
+
+Use `@JsonCodec` on a class, record, enum, or interface to make a codec the
default representation
+for that declaration and its descendants:
+
+```java
+@JsonCodec(MoneyCodec.class)
+public final class Money {}
+
+@JsonCodec(AccountCodec.class)
+public interface Account {}
+
+public final class RetailAccount implements Account {}
+```
+
+The default applies at root `Class` and raw `TypeRef` targets and at
unannotated nested value
+positions. Fory explicitly inherits declarations through superclasses and
interfaces; it does not
+use Java `@Inherited`.
+
+Use the same annotation at a type-use position to select a codec for exactly
that occurrence:
+
+```java
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class Invoice {
+ public @JsonCodec(MoneyCodec.class) Money total;
+ public List<@JsonCodec(MoneyCodec.class) Money> items;
+ public Set<@JsonCodec(MoneyCodec.class) Money> uniqueItems;
+ public Collection<@JsonCodec(MoneyCodec.class) Money> allItems;
+ public Map<String, @JsonCodec(MoneyCodec.class) Money> byName;
+ public Optional<@JsonCodec(MoneyCodec.class) Money> optional;
+ public AtomicReference<@JsonCodec(MoneyCodec.class) Money> current;
+
+ // Codec for each element.
+ public @JsonCodec(MoneyCodec.class) Money[] itemArray;
+
+ // Codec for the complete array value.
+ public Money @JsonCodec(MoneyArrayCodec.class) [] encodedArray;
+
+ // Codec for each complete inner Set value.
+ public List<@JsonCodec(MoneySetCodec.class) Set<Money>> groups;
+
+ // Normal containers with a codec only at the leaf.
+ public List<Set<@JsonCodec(MoneyCodec.class) Money>> nestedItems;
+}
+```
+
+Every array dimension is a distinct type-use node. This recursive model also
covers
+multidimensional arrays, `AtomicReferenceArray`, concrete containers, and
generic container
+subclasses. Fory follows the inherited `Collection<E>` or `Map<K,V>` binding
instead of assuming a
+fixed type-argument index.
+
+One logical property's field, effective getter, effective setter, record
component, and matching
+creator parameter contribute to one resolved codec tree. Missing annotations
are not conflicts;
+equal codec classes at one node merge, while different classes fail with both
sources and the
+nested path. Type-use metadata survives generic substitution, including a
codec attached to a
+bound type argument used by a field declared as a type variable. Supported
positions include field
+and record-component types, getter return types, setter value parameters, and
`JsonCreator`
+constructor or factory value parameters.
+
+`TypeRef` preserves ordinary Java generic types but not arbitrary occurrence
annotations. A root
+`TypeRef<List<@JsonCodec(...) Money>>` therefore cannot carry that local
override. Put it on a
+discovered model property, declare a default on `Money`, or use exact builder
registration.
+
+### Codec precedence
+
+Fory chooses the complete codec for each current resolved value node using
this exact order. An
+invalid higher-priority source fails rather than falling back.
+
+| Priority | Source | Matching rule
|
+| -------: | --------------------------------------- |
------------------------------------------------------------------------- |
+| 1 | Current `TYPE_USE @JsonCodec` | Exact resolved
occurrence after property merging and generic substitution |
+| 2 | `registerCodec(Target.class, instance)` | Exact target `Class`;
never inherited |
+| 3 | Direct `@JsonCodec` declaration | Current class, record,
enum, or interface |
+| 4 | Inherited `@JsonCodec` declaration | Most-specific
superclass/interface result |
+| 5 | Existing mapping | `JsonSubTypes`,
built-in/container mapping, then default object mapping |
+
+Rows 1 through 4 replace the whole current value mapping. They may therefore
replace a scalar,
+enum, container, object, or `JsonSubTypes` representation. A current type-use,
exact registration,
+or direct declaration also resolves any lower-priority inherited ambiguity.
+
+### Deterministic inheritance and conflicts
+
+For inherited declarations, Fory collects every annotated proper superclass
and interface, then
+removes a candidate when another declaring type is its Java subtype. The
remaining most-specific
+declarations resolve as follows:
+
+- one declaration wins;
+- unrelated declarations naming the same codec class are consistent;
+- unrelated declarations naming different codec classes conflict.
+
+This is independent of `implements` order and reflection order. A child
interface declaration
+overrides its parent declaration, and a child class declaration overrides its
parent:
+
+```java
+@JsonCodec(ParentCodec.class)
+interface Parent {}
+
+@JsonCodec(ChildCodec.class)
+interface Child extends Parent {}
+
+final class Value implements Child {} // ChildCodec
+
+@JsonCodec(BaseCodec.class)
+class Base {}
+
+@JsonCodec(ValueCodec.class)
+final class Concrete extends Base {} // ValueCodec
+```
+
+Unrelated interfaces using a common codec are valid; different codecs are
ambiguous:
+
+```java
+@JsonCodec(CommonCodec.class)
+interface A {}
+
+@JsonCodec(CommonCodec.class)
+interface B {}
+
+final class Consistent implements A, B {}
+
+@JsonCodec(ACodec.class)
+interface Left {}
+
+@JsonCodec(BCodec.class)
+interface Right {}
+
+final class Ambiguous implements Left, Right {} // Metadata error.
+```
+
+Superclass/interface candidates follow the same subtype rule:
+
+| Declaring-type relationship | Codec classes | Result
|
+| ----------------------------------------------- | ----------------- |
--------------------------------------------------------- |
+| Superclass implements or inherits the interface | Same or different |
Superclass declaration wins because it is more specific |
+| Superclass and interface are unrelated | Same | Common
codec is accepted |
+| Superclass and interface are unrelated | Different |
Conflict; class does not automatically override interface |
+
+Disambiguate by annotating the concrete class, annotating only the current
type use, or registering
+an exact codec instance:
+
+```java
+@JsonCodec(ConcreteCodec.class)
+final class DeclaredValue implements Left, Right {}
+
+final class Holder {
+ public @JsonCodec(LocalCodec.class) Ambiguous value;
+}
+
+ForyJson json =
+ ForyJson.builder()
+ .registerCodec(Ambiguous.class, new ConcreteCodec())
+ .build();
+```
+
+Fory retains Jackson's useful idea of explicitly inheriting annotations
through class and
+interface hierarchies, but rejects traversal-order first-wins behavior. Java
subtype specificity
+and codec-class identity decide the result; incomparable different codecs
produce an error instead
+of depending on hierarchy declaration order.
+
+### Nested composition and Map keys
+
+A custom codec owns its complete current value and cannot delegate annotated
children. An explicit
+nested type-use under an outer custom codec is therefore rejected as hidden
configuration:
+
+```java
+// Invalid: LocalCodec cannot run because CustomListCodec owns the whole list.
+public @JsonCodec(CustomListCodec.class)
+ List<@JsonCodec(LocalCodec.class) Money> values;
+```
+
+A child class's declaration codec is only a lazy default. The outer codec
prevents child lookup,
+so that default is not a hidden explicit instruction and is valid:
+
+```java
+@JsonCodec(MoneyCodec.class)
+final class DeclaredMoney {}
+
+public @JsonCodec(CustomListCodec.class) List<DeclaredMoney> values;
+```
+
+The hidden-explicit-descendant rule also applies when the outer complete codec
comes from builder
+registration or a class/interface declaration. Without an outer custom codec,
arrays, collections,
+map values, Optional, and atomic references resolve their annotated children
normally.
+
+Map keys are JSON object member names, not JSON values. An explicit
`@JsonCodec` anywhere in a Map
+key subtree is rejected. Builder or declaration value codecs on the key's raw
type are ignored in
+key position and still apply when that type is used as a value. For
`JsonAnyProperty`,
+`JsonAnyGetter`, and `JsonAnySetter`, the outer Map is flattened and cannot
select a whole-value
+codec; only its value node can. Wildcards, wildcard bounds, and type variables
still unresolved
+after substitution cannot select a codec. A type-use on an `extends` or
`implements` clause is not
+a JSON value occurrence and is not used; annotate the type declaration
instead. Annotation type
+declarations are not supported JSON model targets.
+
+### Construction, inherited results, and platform support
+
+An annotation codec class must be public, concrete, top-level or static
nested, and have a public
+no-argument constructor. One successfully constructed instance is shared by
all annotated sites
+and concurrent operations of the built `ForyJson`; it must be thread-safe. Use
+`registerCodec(Target.class, instance)` when the codec needs configuration.
+
+In a named Java module, export or open the codec's package to
`org.apache.fory.json`. Fory does not
+bypass a closed module boundary and reports an actionable access error.
+
+When a parent or interface declaration supplies the codec for a more specific
target, every reader
+result must be null or assignable to that target. A parent codec may return
the requested child and
+succeed. Returning a plain parent while decoding the child fails with
`ForyJsonException` that
+names the target type, codec class, declaring type, and actual returned type.
The actual result,
+not the parent's `final` status or the codec's generic signature, determines
validity.
+
+`@JsonCodec` is supported on ordinary JVMs. Android and GraalVM native image
ignore both declaration
+and type-use forms, avoiding partial behavior when nested runtime type
metadata is unavailable. Use
+exact `registerCodec` registration there. Native-image reflection
configuration is not a supported
+way to enable part of this annotation feature.
## Type validation and untrusted input
@@ -718,7 +947,8 @@ ForyJson json =
Allow every application model and non-built-in container type that the
declared schema uses. The
checker applies while application types are prepared for serialization and
parsing and must be
-thread-safe. Built-in scalars normally skip the custom checker. Custom codecs
cannot bypass the
+thread-safe. Built-in scalars normally skip the custom checker, but selecting
an application codec
+for a built-in target makes that target subject to the checker. Custom codecs
cannot bypass the
fixed disallow list.
`withClassLoader` fixes subtype `className` resolution. Otherwise `build()`
snapshots the thread
@@ -753,6 +983,7 @@ Circular graphs eventually fail `maxDepth`; they are not
reconstructed.
| Ordinary object cannot be created | Add a usable no-argument constructor,
use a record or creator, or register a codec |
| Ordinary accessor annotation fails | Use an eligible public JavaBean
accessor and disable field mode |
| Any annotation fails | Use one field form or one valid method
pair with resolved `Map<String, V>` types |
+| Codec annotation fails | Resolve same-node or hierarchy
conflicts, hidden nested overrides, or codec constructor access |
| Subtype fails | Write with the declared base, list the
exact runtime class, and use the configured wire shape |
| Collection fails | Target a supported interface/common
implementation or register a codec |
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]