Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/run_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ run_graalvm_tests() {
# GraalVM jobs consume production jars only; Java CI owns test/source jar verification.
# Run the install goal directly after package so verify is not repeated in every native job.
mvn -T10 -B --no-transfer-progress clean package install:install \
-pl .,fory-test-core,fory-core,fory-json \
-pl .,fory-test-core,fory-core,fory-json,fory-annotation-processor \
-Dmaven.test.skip=true \
-Dmaven.source.skip=true \
-Dmaven.javadoc.skip=true
Expand Down
3 changes: 2 additions & 1 deletion ci/tasks/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ def run_graalvm_tests(main_class):
# production multi-release jars consumed by native-image.
common.exec_cmd(
"mvn -T10 -B --no-transfer-progress clean package install:install "
"-pl .,fory-test-core,fory-core,fory-json -Dmaven.test.skip=true "
"-pl .,fory-test-core,fory-core,fory-json,fory-annotation-processor "
"-Dmaven.test.skip=true "
"-Dmaven.source.skip=true -Dmaven.javadoc.skip=true"
)

Expand Down
22 changes: 14 additions & 8 deletions docs/guide/java/android-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ Child codecs act on one direct level only. For example, `elementCodec` on `Money
`Money[]`, and `elementCodec` on `AtomicReferenceArray<Money>` handles each `Money`. Use a complete
`value` codec when deeper custom behavior is required.

`@JsonType` is optional. Add the annotation processor and mark application models with `JsonType`
when the build should generate exact R8 rules:
Add the annotation processor and mark application object models with `JsonType` to generate direct
field, getter, setter, Record constructor, and `JsonCreator` operations together with exact R8
rules:

```kotlin
dependencies {
Expand All @@ -99,7 +100,8 @@ public final class Invoice {
}
```

Applications that omit `JsonType` can supply equivalent exact rules themselves. Retain every model
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
by Fory JSON, plus the public no-argument constructor of every annotation-selected codec. For the
previous `Invoice` example:
Expand All @@ -118,8 +120,7 @@ previous `Invoice` example:
```

The same exact-rule approach supports every `JsonCodec` member; it is not limited to complete-value
codecs. `JsonType` only automates rule generation on Android and is not required for codec
selection.
codecs. `JsonType` is not required for codec selection on an ordinary class.

For `@JsonType` models, the generated R8 rules also retain `JsonValue` fields and effective methods,
fixed `JsonRawValue` and `JsonBase64` fields and getters, their runtime annotations, and the Base64
Expand All @@ -131,8 +132,14 @@ name that method explicitly.
Android Fory JSON requires a retained no-argument constructor for an ordinary mutable class; it may
be non-public when Android reflection can make it accessible. `JsonCreator` constructor-backed
classes follow the normal creator rules instead. Retain every field and method used for reflection,
or use an application codec when a model cannot satisfy those requirements. Fory JSON Record
mapping is not supported on Android.
or use an application codec when a model cannot satisfy those requirements.

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; use a complete value codec for deeper nested behavior.

## Static Generated Serializers

Expand Down Expand Up @@ -207,7 +214,6 @@ The following JVM features are not supported on Android:
- Native-address serialization APIs and native-address `MemoryBuffer` wrapping.
- Raw unsafe memory copy APIs.
- `java/fory-format` row-format APIs.
- Fory JSON Record mapping.

## ByteBuffer

Expand Down
33 changes: 24 additions & 9 deletions docs/guide/java/graalvm-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,20 @@ compilation is unavailable.

## Fory JSON

Fory JSON uses a separate Native Image workflow. It has no type-registration API and does not
create a `ForyJson` instance or generate JSON codecs while the image is built. Add `@JsonType` to
each object model that the native executable reads or writes:
Fory JSON uses a separate Native Image workflow. Add the Fory annotation processor to the
application compiler path:

```xml
<annotationProcessorPaths>
<path>
<groupId>org.apache.fory</groupId>
<artifactId>fory-annotation-processor</artifactId>
<version>${fory.version}</version>
</path>
</annotationProcessorPaths>
```

Then add `@JsonType` to each concrete object model that the native executable reads or writes:

```java
import org.apache.fory.json.ForyJson;
Expand All @@ -69,16 +80,19 @@ public class JsonExample {
}
```

The `fory-json` artifact activates its Native Image Feature automatically. Reachable `@JsonType`
classes gate object-model metadata. `@JsonType` is not inherited, so annotate every concrete runtime
model. An annotated base with a class-literal `@JsonSubTypes` table registers those listed subtypes
automatically. Reachable concrete `Collection` and `Map` root types are also supported when they
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
with a class-literal `@JsonSubTypes` table registers those listed subtypes automatically, but each
concrete object subtype needs its own direct `@JsonType` to receive generated operations. Reachable
concrete `Collection` and `Map` root types are also supported when they
have the public no-argument constructor required by Fory JSON. Reachable `@JsonCodec` declarations
register their codec constructor even when the declaration target is not an object model. A class
referenced only by a runtime string is not reachable; `JsonSubTypes.Type.className` is therefore
unsupported in a native image.

Native execution uses Fory JSON's interpreted object codec. `ForyJson.builder()` automatically
Native execution uses Fory JSON's interpreted readers and writers with the generated property and
creator operations. `ForyJson.builder()` automatically
disables runtime code generation and asynchronous compilation in the native executable, while all
other builder options retain their normal behavior. Applications can create differently configured
`ForyJson` instances at runtime and do not need build-time initialization or reflection
Expand All @@ -93,7 +107,8 @@ on the JVM and Android.
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.
retains these members and the Base64 codec constructor. A directly annotated `JsonValue` Record
uses its generated component accessor and canonical constructor operations.

`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
Expand Down
18 changes: 12 additions & 6 deletions docs/guide/java/json-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,15 @@ import org.apache.fory.json.annotation.JsonType;
import org.apache.fory.json.annotation.JsonValue;
```

`JsonType` marks a reachable object model for GraalVM Native Image metadata. On Android it enables
processor-generated R8 rules. It has no effect on ordinary JVM JSON behavior and is not inherited.
See [GraalVM Support](graalvm-support.md) and
[Android Support](android-support.md) for the platform workflows.
`JsonType` asks the annotation processor to generate direct property and creator operations plus
the exact retention rules for an eligible concrete object model. A directly annotated
`JsonValue` Record also receives a companion so its value accessor and canonical constructor work
after Android desugaring. The same generated companion is used on the JVM, Android, and GraalVM
Native Image. The annotation is not inherited; a concrete subtype needs its own direct annotation
to receive a companion. See
[GraalVM Support](graalvm-support.md) and [Android Support](android-support.md) for setup.
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.

### `JsonProperty`

Expand Down Expand Up @@ -999,8 +1004,9 @@ 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. Android applications may use `JsonType` for generated exact R8 rules or provide the
equivalent rules themselves. 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 `JsonType` and the processor.
GraalVM object models follow the `JsonType` workflow in
[GraalVM Support](graalvm-support.md).

## Type validation and untrusted input
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/java/static-generated-serializers.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The processor generates serializer classes in the same Java package as the annot
- `Order_ForyNativeSerializer` for Java native mode.

For a static nested type such as `Outer.Inner`, the generated top-level classes are
`Outer_Inner_ForySerializer` and `Outer_Inner_ForyNativeSerializer`.
`Outer_d_Inner_ForySerializer` and `Outer_d_Inner_ForyNativeSerializer`.

## Field Debug Tracing

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/kotlin/static-generated-serializers.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ Generated Schema IDL modules use the same path. They call

The generated serializer is emitted in the same package as the target class.
Its name is `<target>_ForySerializer`. For nested binary names, `$` is encoded
as `_`; source underscores are encoded as `_u_`.
as `_d_`; source underscores are encoded as `_u_`.

These names are an implementation detail. They matter for diagnostics and
Android shrinking, but user code should only register target classes.
Expand Down
6 changes: 4 additions & 2 deletions integration_tests/android_tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
This project runs Android API 26+ instrumented tests for Java `fory-core` and
`fory-json`. API 26 runs both debug reflection coverage and the release-minified
suite. API 36 runs the release-minified suite. Release coverage verifies static
serializers, processor-generated Fory JSON rules for ordinary codec declarations,
and equivalent application-authored exact rules.
serializers, processor-generated Fory JSON execution for mutable classes,
`JsonCreator` classes, object-mapped and `JsonValue` desugared Records, generated
retention rules, and equivalent application-authored exact rules for
unannotated ordinary classes.

The tests consume `org.apache.fory:fory-core:1.4.0-SNAPSHOT`,
`org.apache.fory:fory-json:1.4.0-SNAPSHOT`, and
Expand Down
5 changes: 5 additions & 0 deletions integration_tests/android_tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ android {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

testBuildType foryTestBuildType

buildTypes {
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/android_tests/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
public static void plainReflectionWithoutRules(boolean);
public static void manualPlainRules();
public static void generatedPlainRules();
public static void generatedRecord();
public static void generatedValueRecord();
public static void manualCodecs();
public static void generatedCodecs();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ public void generatedPlainJsonRules() {
AndroidJsonScenarios.generatedPlainRules();
}

@Test
public void generatedRecordJson() {
AndroidJsonScenarios.generatedRecord();
}

@Test
public void generatedValueRecordJson() {
AndroidJsonScenarios.generatedValueRecord();
}

@Test
public void manualJsonCodecs() {
AndroidJsonScenarios.manualCodecs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonType;

/** Android acceptance scenarios for reflection, declaration codecs, and R8 retention. */
public final class AndroidJsonScenarios {
Expand Down Expand Up @@ -62,6 +63,41 @@ public static void generatedPlainRules() {
checkEquals("generated-plain", decoded.name);
}

public static void generatedRecord() {
try {
Class.forName(
"org.apache.fory.android.GeneratedJsonRecord_ForyJsonCodec",
false,
GeneratedJsonRecord.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new AssertionError("generated Record JSON codec was removed", e);
}
check(GeneratedJsonRecord.class.isAnnotationPresent(JsonType.class));
ForyJson json = ForyJson.builder().build();
String encoded = json.toJson(new GeneratedJsonRecord(30, "android"));
checkEquals("{\"id\":30,\"name\":\"ANDROID\"}", encoded);
GeneratedJsonRecord decoded = json.fromJson(encoded, GeneratedJsonRecord.class);
checkEquals(30, decoded.id());
checkEquals("ANDROID", decoded.name());
}

public static void generatedValueRecord() {
try {
Class.forName(
"org.apache.fory.android.GeneratedJsonValueRecord_ForyJsonCodec",
false,
GeneratedJsonValueRecord.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new AssertionError("generated value Record JSON codec was removed", e);
}
ForyJson json = ForyJson.builder().build();
GeneratedJsonValueRecord value = new GeneratedJsonValueRecord("android-value");
checkEquals("\"android-value\"", json.toJson(value));
checkEquals(
"decoded-value",
json.fromJson("\"decoded-value\"", GeneratedJsonValueRecord.class).value());
}

public static void manualCodecs() {
ForyJson json = ForyJson.builder().build();
ManualJsonModel value = new ManualJsonModel();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fory.android;

import java.util.Locale;
import org.apache.fory.json.annotation.JsonType;

/** Processor-generated JSON execution fixture for an Android-desugared Record. */
@JsonType
public record GeneratedJsonRecord(int id, String name) {
@Override
public String name() {
return name.toUpperCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fory.android;

import org.apache.fory.json.annotation.JsonCreator;
import org.apache.fory.json.annotation.JsonType;
import org.apache.fory.json.annotation.JsonValue;

/** Processor-generated value execution fixture for an Android-desugared Record. */
@JsonType
public record GeneratedJsonValueRecord(@JsonValue String value) {
@JsonCreator
public GeneratedJsonValueRecord {}
}
7 changes: 7 additions & 0 deletions integration_tests/graalvm_tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.source}</target>
<annotationProcessorPaths>
<path>
<groupId>org.apache.fory</groupId>
<artifactId>fory-annotation-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.fory.graalvm.closed.ClosedJsonRecord;
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.PropertyNamingStrategy;
import org.apache.fory.json.annotation.JsonAnyProperty;
Expand Down Expand Up @@ -69,9 +70,17 @@ public static void main(String[] args) {
testGenericProperties();
testBigDecimal();
testSqlTypes();
testClosedPackage();
System.out.println("Fory JSON succeed");
}

private static void testClosedPackage() {
ForyJson json = ForyJson.builder().build();
ClosedJsonRecord value = new ClosedJsonRecord(17, "closed");
String encoded = json.toJson(value);
Preconditions.checkArgument(json.fromJson(encoded, ClosedJsonRecord.class).equals(value));
}

private static void testModels() {
ForyJson json = ForyJson.builder().build();
Model value = new Model();
Expand Down
Loading
Loading