From 5275c5bac9f2428c41b3e72bd0bc45c637ceb9b6 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Thu, 17 Sep 2026 16:17:29 +0800 Subject: [PATCH 1/2] feat(kotlin): support runtime Kotlin JSON type tokens --- docs/json/kotlin.md | 61 +++++++++++++++ docs/json/troubleshooting.md | 43 +++++------ kotlin/fory-json-kotlin/README.md | 4 + kotlin/fory-json-kotlin/pom.xml | 6 ++ .../apache/fory/json/kotlin/KotlinTypeRefs.kt | 18 ++++- .../json/kotlin/KotlinTypeRefRuntimeTest.kt | 74 +++++++++++++++++++ 6 files changed, 183 insertions(+), 23 deletions(-) diff --git a/docs/json/kotlin.md b/docs/json/kotlin.md index 5b00969d2e..511a634f10 100644 --- a/docs/json/kotlin.md +++ b/docs/json/kotlin.md @@ -108,6 +108,67 @@ val json = ForyJson.builder().withModule(ForyJsonKotlin).build() There is no automatic classpath installation or Kotlin-specific encode/decode alias. +## Runtime types and framework integration + +Use `jsonTypeRef(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(val flag: Boolean, val data: T? = null, val msg: String? = null) + +class EmployeeController { + fun employees(): Response> = + Response(true, listOf(Employee(1, "Alice"))) +} + +val json = ForyJsonKotlin.builder().build() +val method = EmployeeController::class.java.getMethod("employees") +val responseType = jsonTypeRef(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>>` is present, select the body type +`Response>` 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. + ## Immutable classes and compiler defaults An ordinary or data class is mapped as a named JSON object. Fory selects one valid public Kotlin diff --git a/docs/json/troubleshooting.md b/docs/json/troubleshooting.md index 5d301fddf5..fc37affecc 100644 --- a/docs/json/troubleshooting.md +++ b/docs/json/troubleshooting.md @@ -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` 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()`; `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` 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()`; `in` and star projections cannot reconstruct one exact schema | +| Kotlin generic fails in a framework converter | Preserve the declared Kotlin `KType` and use `jsonTypeRef(kType)`; Java `TypeRef.of(type)` does not restore Kotlin nullability. See [framework integration](kotlin.md#runtime-types-and-framework-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 diff --git a/kotlin/fory-json-kotlin/README.md b/kotlin/fory-json-kotlin/README.md index 2092827f96..9c851b72bb 100644 --- a/kotlin/fory-json-kotlin/README.md +++ b/kotlin/fory-json-kotlin/README.md @@ -69,6 +69,10 @@ Construct each `jsonTypeRef()` 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`. +For framework callbacks with a runtime Kotlin `KType`, use `jsonTypeRef(kType)`. +See [runtime types and framework integration](../../docs/json/kotlin.md#runtime-types-and-framework-integration) +for controller method discovery and request/response conversion. + `ForyJsonKotlin.builder()` is equivalent to installing the module explicitly: ```kotlin diff --git a/kotlin/fory-json-kotlin/pom.xml b/kotlin/fory-json-kotlin/pom.xml index a505472904..bdf7318b00 100644 --- a/kotlin/fory-json-kotlin/pom.xml +++ b/kotlin/fory-json-kotlin/pom.xml @@ -106,6 +106,12 @@ kotlin-metadata-jvm ${kotlin.version} + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + test + org.jetbrains.kotlin kotlin-test-testng diff --git a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefs.kt b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefs.kt index 42614a92b4..8e1a4417d7 100644 --- a/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefs.kt +++ b/kotlin/fory-json-kotlin/src/main/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefs.kt @@ -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 jsonTypeRef(): TypeRef = jsonTypeRef(typeOf()) + +/** + * 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 jsonTypeRef(): TypeRef = - KotlinTypeRefs.from(typeOf()) as TypeRef +public fun jsonTypeRef(type: KType): TypeRef = KotlinTypeRefs.from(type) as TypeRef /** Kotlin/JVM type-token conversion used by public reified roots and metadata model discovery. */ @OptIn(ExperimentalUnsignedTypes::class) diff --git a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefRuntimeTest.kt b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefRuntimeTest.kt index 876754dc42..8c1c8a56d7 100644 --- a/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefRuntimeTest.kt +++ b/kotlin/fory-json-kotlin/src/test/kotlin/org/apache/fory/json/kotlin/KotlinTypeRefRuntimeTest.kt @@ -19,6 +19,9 @@ package org.apache.fory.json.kotlin +import java.io.ByteArrayOutputStream +import kotlin.reflect.jvm.kotlinFunction +import kotlin.reflect.typeOf import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -27,6 +30,7 @@ import org.apache.fory.json.ForyJsonException import org.apache.fory.json.annotation.JsonMixin import org.apache.fory.json.annotation.JsonMixinRemove import org.apache.fory.json.annotation.JsonSubTypes +import org.apache.fory.reflect.TypeRef private typealias StringTokenBox = TokenBox @@ -36,6 +40,20 @@ data class CovariantBox(val value: T) data class NonNullBoundBox(val value: T) +data class TokenResponse(val flag: Boolean, val data: T? = null, val msg: String? = null) + +data class TokenEmployee(val id: Long, val name: String) + +class TokenController { + fun employees(): TokenResponse> = + TokenResponse(true, listOf(TokenEmployee(1, "Alice"))) + + fun nullableEmployees(): TokenResponse> = + TokenResponse(true, listOf(TokenEmployee(1, "Alice"), null)) + + fun echo(value: T): T = value +} + class InvariantBox(val value: T) { override fun equals(other: Any?): Boolean = other is InvariantBox<*> && value == other.value @@ -79,6 +97,62 @@ data class ContributedProjectionHolder(val value: InvariantBox(method.kotlinFunction!!.returnType) + val value: Any = TokenController().employees() + assertEquals>(jsonTypeRef>>(), type) + forEachJsonMode { json -> + val output = ByteArrayOutputStream() + json.writeJsonTo(value, type, output) + assertEquals( + """{"flag":true,"data":[{"id":1,"name":"Alice"}]}""", + output.toString("UTF-8"), + ) + assertEquals(value, json.fromJson(output.toByteArray(), type)) + assertEquals( + TokenResponse>(true), + json.fromJson("""{"flag":true}""", type) + ) + assertFailsWith { json.fromJson("""{"flag":true,"data":[null]}""", type) } + } + val json = newKotlinJson(KotlinJsonTestMode.INTERPRETED) + assertFailsWith { + json.writeJsonTo(value, TypeRef.of(method.genericReturnType), ByteArrayOutputStream()) + } + } + + @Test + fun reflectedNullableElements() { + val method = TokenController::class.java.getMethod("nullableEmployees") + val type = jsonTypeRef(method.kotlinFunction!!.returnType) + val value = TokenController().nullableEmployees() + assertEquals>(jsonTypeRef>>(), type) + forEachJsonMode { json -> + assertEquals(value, json.fromJson(json.toJsonBytes(value, type), type)) + } + } + + @Test + fun runtimeSemanticTypes() { + val type = jsonTypeRef(typeOf>?>()) + assertEquals>(jsonTypeRef>?>(), type) + val value = TokenBox(listOf(ULong.MAX_VALUE, null)) + forEachJsonMode { json -> + assertEquals(value, json.fromJson(json.toJson(value, type), type)) + assertEquals(null, json.fromJson(json.toJson(null, type), type)) + } + } + + @Test + fun incompleteRuntimeTypes() { + val method = TokenController::class.java.getMethod("echo", Any::class.java) + assertFailsWith { jsonTypeRef(method.kotlinFunction!!.returnType) } + assertFailsWith { jsonTypeRef(typeOf>()) } + assertFailsWith { jsonTypeRef(typeOf>()) } + } + @Test fun typeAliasUsesExpandedBinding() { val alias = jsonTypeRef() From 978db6a8b60779adc670520dd1603e3f99b10fd5 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Thu, 17 Sep 2026 16:33:06 +0800 Subject: [PATCH 2/2] docs(json): add Spring and Kotlin integration guide --- docs/json/index.md | 1 + docs/json/integration.md | 90 +++++++++++++++++++++++++++++++ docs/json/kotlin.md | 63 ++-------------------- docs/json/troubleshooting.md | 44 +++++++-------- kotlin/fory-json-kotlin/README.md | 2 +- 5 files changed, 117 insertions(+), 83 deletions(-) create mode 100644 docs/json/integration.md diff --git a/docs/json/index.md b/docs/json/index.md index 97a436f060..21b728c773 100644 --- a/docs/json/index.md +++ b/docs/json/index.md @@ -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) | diff --git a/docs/json/integration.md b/docs/json/integration.md new file mode 100644 index 0000000000..1af3955de6 --- /dev/null +++ b/docs/json/integration.md @@ -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(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(val flag: Boolean, val data: T? = null, val msg: String? = null) + +class EmployeeController { + fun employees(): Response> = + Response(true, listOf(Employee(1, "Alice"))) +} + +val json = ForyJsonKotlin.builder().build() +val method = EmployeeController::class.java.getMethod("employees") +val responseType = jsonTypeRef(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>>` is present, select the body type +`Response>` 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. diff --git a/docs/json/kotlin.md b/docs/json/kotlin.md index 511a634f10..e45d3b36ce 100644 --- a/docs/json/kotlin.md +++ b/docs/json/kotlin.md @@ -108,66 +108,9 @@ val json = ForyJson.builder().withModule(ForyJsonKotlin).build() There is no automatic classpath installation or Kotlin-specific encode/decode alias. -## Runtime types and framework integration - -Use `jsonTypeRef(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(val flag: Boolean, val data: T? = null, val msg: String? = null) - -class EmployeeController { - fun employees(): Response> = - Response(true, listOf(Employee(1, "Alice"))) -} - -val json = ForyJsonKotlin.builder().build() -val method = EmployeeController::class.java.getMethod("employees") -val responseType = jsonTypeRef(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>>` is present, select the body type -`Response>` 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. +For framework callbacks with a runtime Kotlin `KType`, use `jsonTypeRef(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 diff --git a/docs/json/troubleshooting.md b/docs/json/troubleshooting.md index fc37affecc..d5d08677c6 100644 --- a/docs/json/troubleshooting.md +++ b/docs/json/troubleshooting.md @@ -19,28 +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` 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()`; `in` and star projections cannot reconstruct one exact schema | -| Kotlin generic fails in a framework converter | Preserve the declared Kotlin `KType` and use `jsonTypeRef(kType)`; Java `TypeRef.of(type)` does not restore Kotlin nullability. See [framework integration](kotlin.md#runtime-types-and-framework-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 | +| 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` 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()`; `in` and star projections cannot reconstruct one exact schema | +| Kotlin generic fails in a framework converter | Preserve the declared Kotlin `KType` and use `jsonTypeRef(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 diff --git a/kotlin/fory-json-kotlin/README.md b/kotlin/fory-json-kotlin/README.md index 9c851b72bb..5ab2e02edc 100644 --- a/kotlin/fory-json-kotlin/README.md +++ b/kotlin/fory-json-kotlin/README.md @@ -70,7 +70,7 @@ cannot express, including occurrence nullability, unsigned semantics, value-clas nested generic arguments such as `List`. For framework callbacks with a runtime Kotlin `KType`, use `jsonTypeRef(kType)`. -See [runtime types and framework integration](../../docs/json/kotlin.md#runtime-types-and-framework-integration) +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: