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 fb7a0bcb37 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
fb7a0bcb37 is described below
commit fb7a0bcb37513d79b27440f20ba67c9eee2d50b7
Author: chaokunyang <[email protected]>
AuthorDate: Thu Jul 16 12:13:04 2026 +0000
🔄 synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/java/android-support.md | 49 +++++++++++++---
docs/guide/java/graalvm-support.md | 52 +++++++++++++++--
docs/guide/java/json-support.md | 115 +++++++++++++++++++++++++++++++++----
3 files changed, 191 insertions(+), 25 deletions(-)
diff --git a/docs/guide/java/android-support.md
b/docs/guide/java/android-support.md
index 25421b25ea..0a45194ae5 100644
--- a/docs/guide/java/android-support.md
+++ b/docs/guide/java/android-support.md
@@ -100,6 +100,40 @@ public final class Invoice {
}
```
+The same processor supports Fory JSON Mixins. A Mixin declares one exact
target and is registered
+on the runtime that should use it:
+
+```java
+import org.apache.fory.json.ForyJson;
+import org.apache.fory.json.annotation.JsonBase64;
+import org.apache.fory.json.annotation.JsonMixin;
+
+@JsonMixin(target = ThirdPartyInvoice.class)
+public abstract class ThirdPartyInvoiceMixin {
+ @JsonBase64 byte[] signature;
+}
+
+ForyJson json =
+ ForyJson.builder().registerMixin(ThirdPartyInvoiceMixin.class).build();
+```
+
+Compile every non-empty Mixin source with `fory-annotation-processor`. The
processor emits exact
+R8 rules and any pair-specific target operations that the runtime can use.
Registered codecs,
+effective type codecs, and built-in mappings keep their normal runtime
precedence. An empty Mixin
+produces no generated output.
+
+The target does not need `JsonType` merely because it has a Mixin. `JsonMixin`
is itself the
+processor entry point for the pair. If a target also uses `JsonType`, the
runtime selects the
+pair-specific companion for a non-empty registered Mixin instead of combining
the overlay with the
+target's direct companion.
+
+Only one source is enabled for an exact target in one built runtime. A later
registration for that
+target replaces an earlier registration on the builder, and `build()`
snapshots the selected
+mapping. The processor may generate artifacts for multiple source
alternatives; the runtime uses
+only the last registered source.
+
+Use the processor-generated R8 rules for non-empty Mixins instead of broad
package keep rules.
+
Ordinary non-Record classes that omit `JsonType` can supply equivalent exact
rules themselves.
Retain every model
constructor, field, method, generic signature, declaration annotation, and
parameter annotation used
@@ -137,13 +171,14 @@ supports mutable classes, creator-backed classes, and
Records through their norm
construction paths. When the containing model and its unwrapped children use
`JsonType`, their
generated companions supply those operations.
-Android-desugared Records require a direct `@JsonType` annotation and the
annotation processor.
-Manual R8 rules alone cannot reconstruct Record component order because
Android does not provide
-the Java Record reflection APIs. This also applies to a Record whose complete
representation is a
-`JsonValue` String: the generated companion identifies the propagated
component accessor and calls
-an annotated one-String canonical constructor directly. Generated child codecs
act on one level
-exactly as they do on the JVM. Every Record in a `JsonUnwrapped` path needs
its own direct
-`JsonType`. Use a complete value codec for deeper nested behavior.
+Android-desugared Records require processor-generated operations from either a
direct `@JsonType`
+declaration or a compiled exact `@JsonMixin` pair. Manual R8 rules alone
cannot reconstruct Record
+component order because Android does not provide the Java Record reflection
APIs. This also applies
+to a Record whose complete representation is a `JsonValue` String: the
generated companion
+identifies the propagated component accessor and calls an annotated one-String
canonical
+constructor directly. Generated child codecs act on one level exactly as they
do on the JVM. Every
+Record in a `JsonUnwrapped` path needs its own direct `JsonType` declaration
or compiled exact
+`JsonMixin` pair. Use a complete value codec for deeper nested behavior.
## Static Generated Serializers
diff --git a/docs/guide/java/graalvm-support.md
b/docs/guide/java/graalvm-support.md
index 553f616f45..7da3e7a2a4 100644
--- a/docs/guide/java/graalvm-support.md
+++ b/docs/guide/java/graalvm-support.md
@@ -80,6 +80,43 @@ public class JsonExample {
}
```
+The processor also supports Fory JSON Mixins for models that cannot be
modified:
+
+```java
+import org.apache.fory.json.ForyJson;
+import org.apache.fory.json.annotation.JsonMixin;
+import org.apache.fory.json.annotation.JsonProperty;
+
+@JsonMixin(target = ThirdPartyUser.class)
+public abstract class ThirdPartyUserMixin {
+ @JsonProperty("user_id")
+ long id;
+}
+
+public class JsonExample {
+ public static void main(String[] args) {
+ ForyJson json =
+ ForyJson.builder().registerMixin(ThirdPartyUserMixin.class).build();
+ ThirdPartyUser user = json.fromJson("{\"user_id\":1}",
ThirdPartyUser.class);
+ System.out.println(json.toJson(user));
+ }
+}
+```
+
+`JsonMixin` is a build-time entry point for its exact declared target, so the
target does not need
+`JsonType` solely to use the Mixin. The registered Mixin class literal must be
reachable from the
+application. The processor emits available target operations for each
non-empty Mixin, and the
+Fory JSON Native Image Feature retains the effective runtime metadata. Normal
runtime codec
+precedence still selects the representation. An empty Mixin produces no
generated output.
+
+Only one source is enabled for an exact target in a built `ForyJson`. Later
registration replaces
+an earlier source for subsequent `build()` calls; a runtime keeps the
immutable snapshot it was
+built with. If the target also has a direct `JsonType` companion, a non-empty
registered Mixin
+selects the pair-specific artifact instead of combining the overlay with the
direct companion.
+
+Do not add application reflection configuration as a replacement for the
generated configuration.
+The native executable resolves the same effective annotations as the JVM.
+
The processor generates direct property and creator operations. The
`fory-json` artifact activates
its Native Image Feature automatically and retains the generated factories and
required model
metadata. `@JsonType` is not inherited, so annotate every concrete runtime
model. An annotated base
@@ -106,17 +143,20 @@ on the JVM and Android.
`JsonValue` fields and effective public zero-argument methods are supported,
including matching
one-String `JsonCreator` constructors and public static factories. Fixed
`JsonRawValue` fields and
getters support trusted raw String values, and fixed `JsonBase64` fields and
getters support Base64
-`byte[]` values as on the JVM. Annotate each reachable owning model with
`JsonType` so Native Image
-retains these members and the Base64 codec constructor. A directly annotated
`JsonValue` Record
-uses its generated component accessor and canonical constructor operations.
+`byte[]` values as on the JVM. For direct target annotations, annotate each
reachable owning model
+with `JsonType` so Native Image retains these members and the Base64 codec
constructor. A directly
+annotated `JsonValue` Record uses its generated component accessor and
canonical constructor
+operations. An effective declaration supplied by a Mixin uses the Mixin
workflow above instead.
`JsonAnyProperty` and `JsonAnyGetter` flatten their Map into the enclosing
object. Use
`@JsonCodec(valueCodec = ...)` on that field or getter to customize each
dynamic value. A second
`JsonAnySetter` parameter may use the normal configuration for its own value
shape.
-`JsonUnwrapped` uses the same interpreted behavior as on the JVM. Annotate the
containing model and
-every unwrapped child or intermediate object with `JsonType` so each model
receives its generated
-property and creator operations.
+`JsonUnwrapped` uses the same interpreted behavior as on the JVM. For direct
target annotations,
+annotate the containing model and every unwrapped child or intermediate object
with `JsonType` so
+each model receives its generated property and creator operations. A Mixin
retains the
+unwrapped models reached by its effective schema; register a separate exact
Mixin for a child only
+when that child's annotations also need an overlay.
Child codecs act on one direct level. `elementCodec` supports `Collection`,
Java arrays, and
`AtomicReferenceArray`; `contentCodec` supports `Optional` and
`AtomicReference`; `keyCodec` and
diff --git a/docs/guide/java/json-support.md b/docs/guide/java/json-support.md
index 042320821e..6d3dbfe447 100644
--- a/docs/guide/java/json-support.md
+++ b/docs/guide/java/json-support.md
@@ -295,6 +295,7 @@ are rejected.
| `withConcurrencyLevel` | `max(1, 2 * processors)`
| Reusable operation-state count |
| `withBufferSizeLimitBytes` | 2 MiB
| Reusable capacity retained by each pooled writer |
| `registerCodec` | None
| Exact-class complete-value codec |
+| `registerMixin` | None
| Annotation Mixin for its exact declared target |
| `withTypeChecker` | None
| Application policy in addition to Fory's disallow list |
Depth, concurrency, and retained buffer limits must be positive. The
cached-field-name limit applies
@@ -309,8 +310,9 @@ disabled. Every other builder option keeps the behavior
described above.
Fory JSON provides `JsonProperty`, `JsonPropertyOrder`, `JsonIgnore`,
`JsonAnyProperty`,
`JsonAnyGetter`, `JsonAnySetter`, `JsonCreator`, `JsonCodec`, `JsonValue`,
`JsonRawValue`,
-`JsonBase64`, `JsonUnwrapped`, `JsonSubTypes`, and `JsonType` under
-`org.apache.fory.json.annotation`. They are not Jackson, Gson, or Fory
binary-protocol annotations.
+`JsonBase64`, `JsonUnwrapped`, and `JsonSubTypes` as mapping annotations under
+`org.apache.fory.json.annotation`. `JsonType` is a separate build-time
generation marker. They are
+not Jackson, Gson, or Fory binary-protocol annotations.
```java
import java.util.LinkedHashMap;
@@ -323,6 +325,8 @@ import org.apache.fory.json.annotation.JsonBase64;
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.JsonMixin;
+import org.apache.fory.json.annotation.JsonMixinRemove;
import org.apache.fory.json.annotation.JsonProperty;
import org.apache.fory.json.annotation.JsonPropertyOrder;
import org.apache.fory.json.annotation.JsonRawValue;
@@ -342,6 +346,92 @@ to receive a companion. See
A directly annotated model that uses the default object codec requires that
generated companion;
the runtime reports a configuration error if the processor output is missing.
+### Mixins
+
+Use a Mixin to configure an existing class without changing its source:
+
+```java
+import org.apache.fory.json.ForyJson;
+import org.apache.fory.json.annotation.JsonMixin;
+import org.apache.fory.json.annotation.JsonProperty;
+import org.apache.fory.json.annotation.JsonUnwrapped;
+
+@JsonMixin(target = ThirdPartyUser.class)
+abstract class ThirdPartyUserMixin {
+ @JsonProperty("user_id")
+ long id;
+
+ @JsonUnwrapped(prefix = "address_")
+ Address address;
+}
+
+ForyJson json =
ForyJson.builder().registerMixin(ThirdPartyUserMixin.class).build();
+```
+
+The source must be a named abstract class or interface, must not be local or
anonymous, must not
+extend or implement another type, and is never instantiated. An annotated
source field, method,
+constructor, or parameter selects an existing target declaration by its Java
signature. The source
+cannot invent a Java field, method, constructor, generic type, subtype
implementation, or executable
+body. It may assign supported JSON roles such as property, creator, factory,
subtype table, or value
+representation to matching declarations that already exist on the target.
Every Java type, access
+operation, invocation, and runtime value still comes from the target.
+
+Registration is exact-target only. A base-class registration does not change a
subclass, and an
+interface registration does not change an implementation. A subclass Mixin may
select a member
+that the subclass inherits, but the resulting annotation applies only while
that exact subclass is
+mapped.
+
+All Fory JSON mapping annotations are supported: `JsonAnyGetter`,
`JsonAnyProperty`,
+`JsonAnySetter`, `JsonBase64`, `JsonCodec`, `JsonCreator`, `JsonIgnore`,
`JsonProperty`,
+`JsonPropertyOrder`, `JsonRawValue`, `JsonSubTypes`, `JsonUnwrapped`, and
`JsonValue`. `JsonType`
+cannot be added or removed because it controls build-time generation rather
than the JSON schema.
+
+A source annotation replaces the target annotation of the same type on the
matched declaration.
+The complete annotation is replaced, so omitted members use their declared
defaults instead of
+inheriting values from the target annotation. Other annotation types on that
target declaration
+remain effective.
+
+Use `JsonMixinRemove` to make selected target annotations ineffective:
+
+```java
+import org.apache.fory.json.annotation.JsonMixin;
+import org.apache.fory.json.annotation.JsonMixinRemove;
+import org.apache.fory.json.annotation.JsonRawValue;
+
+@JsonMixin(target = ThirdPartyMessage.class)
+abstract class QuotedMessageMixin {
+ @JsonMixinRemove(JsonRawValue.class)
+ String body;
+}
+```
+
+Removal affects only the matched declaration in the exact-target
configuration. Removing
+`JsonRawValue` restores ordinary quoted String output; removing `JsonBase64`
restores the ordinary
+`byte[]` representation; removing `JsonUnwrapped` restores a nested object
property. Type-level
+removal can mask inherited `JsonCodec` or `JsonPropertyOrder` declarations for
the exact target.
+Removing an absent annotation is harmless, but the selector must still match
exactly one target
+declaration. A source cannot both declare and remove the same annotation type
on one declaration.
+
+Only one source is enabled for an exact target in a built runtime. Registering
a different source
+for the same target replaces the earlier registration, while registering the
same source again is
+idempotent. `build()` snapshots the current last-registration-wins mapping; a
later registration on
+the builder does not change a previously built `ForyJson`. A source with no
mapping annotations is
+a no-op; registering it after another source for the same target clears the
earlier overlay for
+subsequent builds.
+
+A `JsonCodec` supplied by a Mixin is the target's effective annotation and
follows the ordinary
+codec precedence below. In particular, an exact `registerCodec` registration
wins over a type-level
+Mixin codec, while a type-level Mixin codec wins over the built-in mapping for
that target.
+
+Records use their existing field, accessor, and canonical-constructor
parameter declarations. A
+Mixin does not introduce a separate record-component model. Use source
selectors for those real
+declarations and keep repeated annotations consistent as required by normal
record property
+mapping.
+
+On Android and GraalVM Native Image, compile non-empty Mixin sources with the
Fory annotation
+processor so required generated operations and platform configuration are
available. See
+[Android Support](android-support.md) and [GraalVM
Support](graalvm-support.md).
+
### `JsonProperty`
An annotation on a field, getter, or setter configures the complete merged
logical property:
@@ -1026,13 +1116,13 @@ The first `JsonAnySetter` parameter is the String
property name. Its second para
Fory resolves each current value in this order:
-| Priority | Source |
-| -------: | ----------------------------------------- |
-| 1 | Current property or parameter `JsonCodec` |
-| 2 | Exact `registerCodec` registration |
-| 3 | Direct type `JsonCodec` declaration |
-| 4 | Inherited type declaration |
-| 5 | Built-in or default JSON mapping |
+| Priority | Source |
+| -------: | ------------------------------------------------- |
+| 1 | Current property or parameter `JsonCodec` |
+| 2 | Exact `registerCodec` registration |
+| 3 | Exact-target type `JsonCodec` after Mixin overlay |
+| 4 | Inherited-frontier type `JsonCodec` declaration |
+| 5 | Built-in or default JSON mapping |
One logical property may expose the annotation from its field, getter, setter
parameter, creator
parameter, or record propagation. Repeated configurations must be identical;
Fory does not merge
@@ -1059,9 +1149,10 @@ type-declaration codec is used for a more specific
target, every decoded value m
assignable to that target.
The annotation has the same FIELD, METHOD, and PARAMETER behavior on the JVM,
Android, and GraalVM
-Native Image. Ordinary Android classes may omit `JsonType` and provide
equivalent exact rules;
-Android-desugared Records, including `JsonValue` Records, require `JsonType`
and the processor.
-GraalVM object models follow the `JsonType` workflow in
+Native Image. Ordinary Android classes may omit `JsonType` and provide
equivalent exact rules.
+Android-desugared Records, including `JsonValue` Records, require
processor-generated operations
+from either a direct `JsonType` declaration or a compiled exact `JsonMixin`
pair. GraalVM object
+models follow the build-time workflow in
[GraalVM Support](graalvm-support.md).
## Type validation and untrusted input
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]