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
1 change: 1 addition & 0 deletions docs/json/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ reference identity, circular graphs, or Fory's binary-only features.
| Goal | Page |
| --------------------------------------------------------------- | ------------------------------------- |
| First runnable JSON round trip | [Getting Started](getting-started.md) |
| Integrate with Spring and Kotlin framework callbacks | [Integration](integration.md) |
| Understand Java object mapping and configuration | [Object Mapping](object-mapping.md) |
| Configure properties, creators, values, validators, and mixins | [Annotations](annotations.md) |
| Extend complete values, children, and map keys | [Custom Codecs](custom-codecs.md) |
Expand Down
90 changes: 90 additions & 0 deletions docs/json/integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Integration
sidebar_position: 13
id: integration
license: |
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.
---

## Spring Fory

[spring-fory](https://github.com/chaokunyang/spring-fory) provides Spring MVC message converters,
Spring WebFlux codecs, and Spring Boot auto-configuration starters for Fory JSON. It supports
ordinary JSON request and response bodies, with streaming JSON and NDJSON support in WebFlux.

See the project's [installation and usage guide](https://github.com/chaokunyang/spring-fory#installation)
to select the adapter or starter matching your Spring version and configure your application.

## Kotlin integration

Use `jsonTypeRef<Any?>(kType)` when a framework supplies a Kotlin `KType` at runtime and the
callback cannot use a reified type argument. The supplied `KType` determines the complete JSON
type, including nested generic arguments and nullability. `Any?` is only the callback's static
view of the value; it does not replace the supplied type with a dynamic JSON schema.

Obtaining a `KType` from a Kotlin function or a Java `Method` requires the application's
`kotlin-reflect` dependency. Match its version to the application's Kotlin version:

```kotlin
dependencies {
implementation(kotlin("reflect"))
}
```

For example, discover and retain a controller method's response type:

```kotlin
import java.io.OutputStream
import kotlin.reflect.jvm.kotlinFunction
import org.apache.fory.json.kotlin.ForyJsonKotlin
import org.apache.fory.json.kotlin.jsonTypeRef

data class Employee(val id: Long, val name: String)
data class Response<T>(val flag: Boolean, val data: T? = null, val msg: String? = null)

class EmployeeController {
fun employees(): Response<List<Employee>> =
Response(true, listOf(Employee(1, "Alice")))
}

val json = ForyJsonKotlin.builder().build()
val method = EmployeeController::class.java.getMethod("employees")
val responseType = jsonTypeRef<Any?>(requireNotNull(method.kotlinFunction).returnType)

fun writeResponse(value: Any?, output: OutputStream) {
json.writeJsonTo(value, responseType, output)
}

fun readResponse(bytes: ByteArray): Any? = json.fromJson(bytes, responseType)
```

Discover each declared type once and reuse its token. For request bodies, obtain the corresponding
Kotlin value parameter's `KType`. A method with unresolved type parameters still needs its concrete
type arguments before conversion; star projections and contravariant projections remain unsupported.
If you select a static type more specific than `Any?`, the caller must ensure it matches the
supplied `KType`.

For Spring MVC, retain the controller's Kotlin declaration when adapting the request or response
to a converter. A `SmartHttpMessageConverter` can receive application-provided read/write hints
containing that `KType` or its Fory type token. When an HTTP wrapper such as
`ResponseEntity<Response<List<Employee>>>` is present, select the body type
`Response<List<Employee>>` before constructing the token.

An `AbstractHttpMessageConverter` callback that supplies only the runtime object loses generic
arguments. `AbstractGenericHttpMessageConverter` preserves Java generics, but
`TypeRef.of(javaType)` does not restore Kotlin nullability. Neither a Java `Type` nor the runtime
value alone can recover the full Kotlin declaration. Use `jsonTypeRef(kType)` after obtaining that
declaration; this API does not automatically install a Spring converter.
4 changes: 4 additions & 0 deletions docs/json/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ val json = ForyJson.builder().withModule(ForyJsonKotlin).build()

There is no automatic classpath installation or Kotlin-specific encode/decode alias.

For framework callbacks with a runtime Kotlin `KType`, use `jsonTypeRef<Any?>(kType)`.
See [Kotlin integration](integration.md#kotlin-integration) for controller type discovery,
request and response conversion, and Spring MVC adapter requirements.

## Immutable classes and compiler defaults

An ordinary or data class is mapped as a named JSON object. Fory selects one valid public Kotlin
Expand Down
43 changes: 22 additions & 21 deletions docs/json/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,28 @@ license: |
limitations under the License.
---

| Symptom | Likely cause and action |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ForyJsonException` while parsing | Invalid JSON grammar, type mismatch, unsupported mapping, depth or graph-memory violation, validator failure, or trailing content |
| `InsecureException` | Fory's disallow list or the configured `JsonTypeChecker` rejected a class |
| `IllegalArgumentException` from a builder | Check the configured depth, graph-memory, concurrency, retained-buffer, and cached-field-name limits |
| Declared write is rejected | The value is not assignable to the declared type, the type contains a wildcard/type variable, or null was supplied for a primitive |
| Immutable value is not populated | Use a record, a valid `JsonCreator`, or an exact custom codec |
| `JsonValue` read fails | Add one plain `String` `JsonCreator`, or register an exact custom codec |
| Raw JSON output is invalid | Supply exactly one trusted, complete JSON value to the `JsonRawValue` property |
| Ordinary object cannot be constructed | Add a usable no-argument constructor, use a record or `JsonCreator`, or register a custom codec; Android and GraalVM native image are stricter |
| Ordinary accessor annotation fails | The method is not an eligible public JavaBean accessor, or field mode is enabled |
| Any annotation fails | Use exactly one field-backed form or one valid method-backed pair with resolved `Map<String, V>` types; method annotations require non-field mode |
| Codec annotation fails | Resolve same-node or hierarchy conflicts, remove a hidden nested override, or use a public no-argument codec class |
| Subtype is rejected | The base is not declared on the write, the runtime class is not an exact table entry, or the input wire shape differs from the configured inclusion |
| Collection cannot be read | Target a supported interface/common implementation or register a custom codec |
| OutputStream write fails | The underlying `IOException` is wrapped as the cause of `ForyJsonException` |
| Kotlin null or missing member fails | Check the exact `jsonTypeRef`, constructor default, and nullable occurrence; null does not request a compiler default |
| Raw/star/projected Kotlin generic fails | Supply a complete `jsonTypeRef<T>()`; `in` and star projections cannot reconstruct one exact schema |
| Unsupported Kotlin metadata | Ensure the resolved `kotlin-metadata-jvm` supports the model compiler's metadata and that validated JVM members match it |
| Kotlin model fails after Android shrinking | Apply KSP; for an exact Mixin, use it when either its source or target is Kotlin, and verify that the generated rules are packaged |
| Kotlin model is absent in Native Image | Install `ForyJsonKotlin` from a reachable `ForyJsonProvider`, enable code generation, and make the exact binding reachable from that configuration |
| Symptom | Likely cause and action |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ForyJsonException` while parsing | Invalid JSON grammar, type mismatch, unsupported mapping, depth or graph-memory violation, validator failure, or trailing content |
| `InsecureException` | Fory's disallow list or the configured `JsonTypeChecker` rejected a class |
| `IllegalArgumentException` from a builder | Check the configured depth, graph-memory, concurrency, retained-buffer, and cached-field-name limits |
| Declared write is rejected | The value is not assignable to the declared type, the type contains a wildcard/type variable, or null was supplied for a primitive |
| Immutable value is not populated | Use a record, a valid `JsonCreator`, or an exact custom codec |
| `JsonValue` read fails | Add one plain `String` `JsonCreator`, or register an exact custom codec |
| Raw JSON output is invalid | Supply exactly one trusted, complete JSON value to the `JsonRawValue` property |
| Ordinary object cannot be constructed | Add a usable no-argument constructor, use a record or `JsonCreator`, or register a custom codec; Android and GraalVM native image are stricter |
| Ordinary accessor annotation fails | The method is not an eligible public JavaBean accessor, or field mode is enabled |
| Any annotation fails | Use exactly one field-backed form or one valid method-backed pair with resolved `Map<String, V>` types; method annotations require non-field mode |
| Codec annotation fails | Resolve same-node or hierarchy conflicts, remove a hidden nested override, or use a public no-argument codec class |
| Subtype is rejected | The base is not declared on the write, the runtime class is not an exact table entry, or the input wire shape differs from the configured inclusion |
| Collection cannot be read | Target a supported interface/common implementation or register a custom codec |
| OutputStream write fails | The underlying `IOException` is wrapped as the cause of `ForyJsonException` |
| Kotlin null or missing member fails | Check the exact `jsonTypeRef`, constructor default, and nullable occurrence; null does not request a compiler default |
| Raw/star/projected Kotlin generic fails | Supply a complete `jsonTypeRef<T>()`; `in` and star projections cannot reconstruct one exact schema |
| Kotlin generic fails in a framework converter | Preserve the declared Kotlin `KType` and use `jsonTypeRef<Any?>(kType)`; Java `TypeRef.of(type)` does not restore Kotlin nullability. See [framework integration](integration.md#kotlin-integration) |
| Unsupported Kotlin metadata | Ensure the resolved `kotlin-metadata-jvm` supports the model compiler's metadata and that validated JVM members match it |
| Kotlin model fails after Android shrinking | Apply KSP; for an exact Mixin, use it when either its source or target is Kotlin, and verify that the generated rules are packaged |
| Kotlin model is absent in Native Image | Install `ForyJsonKotlin` from a reachable `ForyJsonProvider`, enable code generation, and make the exact binding reachable from that configuration |

Fory JSON mapping, syntax, codec, depth, graph-memory, validator, and output failures use
`ForyJsonException`. User codec code may still throw its own runtime exception. Creator and
Expand Down
4 changes: 4 additions & 0 deletions kotlin/fory-json-kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Construct each `jsonTypeRef<T>()` once and reuse it. It preserves distinctions t
cannot express, including occurrence nullability, unsigned semantics, value-class identity, and
nested generic arguments such as `List<Account?>`.

For framework callbacks with a runtime Kotlin `KType`, use `jsonTypeRef<Any?>(kType)`.
See [Kotlin integration](../../docs/json/integration.md#kotlin-integration)
for controller method discovery and request/response conversion.

`ForyJsonKotlin.builder()` is equivalent to installing the module explicitly:

```kotlin
Expand Down
6 changes: 6 additions & 0 deletions kotlin/fory-json-kotlin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
<artifactId>kotlin-metadata-jvm</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-testng</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,23 @@ import org.apache.fory.type.Types

/** Returns a structural Fory JSON type token which preserves Kotlin nullability and value types. */
@OptIn(ExperimentalStdlibApi::class)
public inline fun <reified T> jsonTypeRef(): TypeRef<T> = jsonTypeRef(typeOf<T>())

/**
* Returns a structural Fory JSON type token for a Kotlin type obtained at runtime.
*
* Use this overload in framework callbacks where a reified type argument is unavailable. [type]
* preserves nested nullability, unsigned types, and value classes. It must describe a complete
* Kotlin type without unresolved type parameters, star projections, or contravariant projections.
*
* [T] provides the caller's static view of the token; it is not inferred from [type] or checked
* against it. Use `Any?` when only the runtime type is known. Construct the token once and reuse
* it.
*
* @throws ForyJsonException if [type] does not describe a supported complete Kotlin type.
*/
@Suppress("UNCHECKED_CAST")
public inline fun <reified T> jsonTypeRef(): TypeRef<T> =
KotlinTypeRefs.from(typeOf<T>()) as TypeRef<T>
public fun <T> jsonTypeRef(type: KType): TypeRef<T> = KotlinTypeRefs.from(type) as TypeRef<T>

/** Kotlin/JVM type-token conversion used by public reified roots and metadata model discovery. */
@OptIn(ExperimentalUnsignedTypes::class)
Expand Down
Loading
Loading