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.git
The following commit(s) were added to refs/heads/main by this push:
new d6a425a15 feat(kotlin): support default value for kotlin data class
(#2416)
d6a425a15 is described below
commit d6a425a1539488803bbdf84aaf64f55290709aa2
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Jul 15 20:17:35 2025 +0800
feat(kotlin): support default value for kotlin data class (#2416)
## What does this PR do?
support default value for kotlin data class
```kotlin
import org.apache.fory.Fory
import org.apache.fory.config.CompatibleMode
import org.apache.fory.serializer.kotlin.KotlinSerializers
// Original data class
data class User(val name: String, val age: Int)
// Evolved data class with new field and default value
data class UserV2(val name: String, val age: Int, val email: String =
"[email protected]")
fun main() {
val fory = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.build()
KotlinSerializers.registerSerializers(fory)
fory.register(User::class.java)
fory.register(UserV2::class.java)
// Serialize with old schema
val oldUser = User("John", 30)
val serialized = fory.serialize(oldUser)
// Deserialize with new schema - missing field gets default value
val newUser = fory.deserialize(serialized, UserV2::class.java)
println(newUser) // UserV2(name=John, age=30, [email protected])
}
```
## Related issues
Closes #1966
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
-->
---
.../fory/builder/MetaSharedCodecBuilder.java | 29 +++-
.../fory/serializer/MetaSharedSerializer.java | 37 +++--
.../org/apache/fory/util/DefaultValueUtils.java | 2 +-
.../fory-core/native-image.properties | 3 +
kotlin/README.md | 70 +++++++++
kotlin/pom.xml | 5 +
.../fory/serializer/kotlin/KotlinSerializers.java | 2 +
.../serializer/kotlin/KotlinDefaultValueSupport.kt | 151 +++++++++++++++++++
.../fory/serializer/kotlin/DefaultValueTest.kt | 163 +++++++++++++++++++++
9 files changed, 445 insertions(+), 17 deletions(-)
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
index ece643cde..c6da767a3 100644
---
a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
@@ -72,6 +72,7 @@ import org.apache.fory.util.record.RecordUtils;
*/
public class MetaSharedCodecBuilder extends ObjectCodecBuilder {
private final ClassDef classDef;
+ private final String defaultValueLanguage;
private final DefaultValueUtils.DefaultValueField[] defaultValueFields;
public MetaSharedCodecBuilder(TypeRef<?> beanType, Fory fory, ClassDef
classDef) {
@@ -91,14 +92,32 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
objectCodecOptimizer =
new ObjectCodecOptimizer(beanClass, grouper,
!fory.isBasicTypesRefIgnored(), ctx);
+ String defaultValueLanguage = "None";
+ DefaultValueUtils.DefaultValueField[] defaultValueFields =
+ new DefaultValueUtils.DefaultValueField[0];
if (fory.getConfig().isScalaOptimizationEnabled()) {
// Check if this is a Scala case class and build default value fields
- this.defaultValueFields =
+ defaultValueFields =
DefaultValueUtils.getScalaDefaultValueSupport()
.buildDefaultValueFields(fory, beanClass,
grouper.getSortedDescriptors());
- } else {
- this.defaultValueFields = new DefaultValueUtils.DefaultValueField[0];
+ if (defaultValueFields.length > 0) {
+ defaultValueLanguage = "Scala";
+ }
+ }
+ if (defaultValueFields.length == 0) {
+ DefaultValueUtils.DefaultValueSupport kotlinDefaultValueSupport =
+ DefaultValueUtils.getKotlinDefaultValueSupport();
+ if (kotlinDefaultValueSupport != null) {
+ defaultValueFields =
+ kotlinDefaultValueSupport.buildDefaultValueFields(
+ fory, beanClass, grouper.getSortedDescriptors());
+ if (defaultValueFields.length > 0) {
+ defaultValueLanguage = "Kotlin";
+ }
+ }
}
+ this.defaultValueLanguage = defaultValueLanguage;
+ this.defaultValueFields = defaultValueFields;
}
// Must be static to be shared across the whole process life.
@@ -155,7 +174,6 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
protected void addCommonImports() {
super.addCommonImports();
ctx.addImport(GeneratedMetaSharedSerializer.class);
- ctx.addImport(DefaultValueUtils.class);
}
// Invoked by JIT.
@@ -232,6 +250,7 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
if (typeRef.unwrap().isPrimitive() || typeRef.equals(STRING_TYPE)) {
defaultValueExpr = new Literal(defaultValue, typeRef);
} else {
+ String funcName = "get" + defaultValueLanguage + "DefaultValue";
defaultValueExpr =
getOrCreateField(
true,
@@ -241,7 +260,7 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
Expression expr =
new StaticInvoke(
DefaultValueUtils.class,
- "getScalaDefaultValue",
+ funcName,
OBJECT_TYPE,
staticBeanClassExpr(),
Literal.ofString(member.getName()));
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
index 3d1bce876..56917ccf0 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
@@ -78,7 +78,7 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
private Serializer<T> serializer;
private final ClassInfoHolder classInfoHolder;
private final SerializationBinding binding;
- private final boolean hasScalaDefaultValues;
+ private final boolean hasDefaultValues;
private final DefaultValueUtils.DefaultValueField[] defaultValueFields;
public MetaSharedSerializer(Fory fory, Class<T> type, ClassDef classDef) {
@@ -114,16 +114,31 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
recordInfo = null;
}
binding = SerializationBinding.createBinding(fory);
+ boolean hasDefaultValues = false;
+ DefaultValueUtils.DefaultValueField[] defaultValueFields =
+ new DefaultValueUtils.DefaultValueField[0];
+ DefaultValueUtils.DefaultValueSupport defaultValueSupport = null;
if (fory.getConfig().isScalaOptimizationEnabled()) {
- hasScalaDefaultValues =
-
DefaultValueUtils.getScalaDefaultValueSupport().hasDefaultValues(type);
- defaultValueFields =
- DefaultValueUtils.getScalaDefaultValueSupport()
- .buildDefaultValueFields(fory, type,
descriptorGrouper.getSortedDescriptors());
- } else {
- hasScalaDefaultValues = false;
- defaultValueFields = new DefaultValueUtils.DefaultValueField[0];
+ defaultValueSupport = DefaultValueUtils.getScalaDefaultValueSupport();
+ if (defaultValueSupport != null) {
+ hasDefaultValues = defaultValueSupport.hasDefaultValues(type);
+ defaultValueFields =
+ defaultValueSupport.buildDefaultValueFields(
+ fory, type, descriptorGrouper.getSortedDescriptors());
+ }
+ }
+ if (!hasDefaultValues) {
+ DefaultValueUtils.DefaultValueSupport kotlinDefaultValueSupport =
+ DefaultValueUtils.getKotlinDefaultValueSupport();
+ if (kotlinDefaultValueSupport != null) {
+ hasDefaultValues = kotlinDefaultValueSupport.hasDefaultValues(type);
+ defaultValueFields =
+ kotlinDefaultValueSupport.buildDefaultValueFields(
+ fory, type, descriptorGrouper.getSortedDescriptors());
+ }
}
+ this.hasDefaultValues = hasDefaultValues;
+ this.defaultValueFields = defaultValueFields;
}
@Override
@@ -215,7 +230,7 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
}
// Set default values for missing fields in Scala case classes
- if (hasScalaDefaultValues) {
+ if (hasDefaultValues) {
DefaultValueUtils.setDefaultValues(obj, defaultValueFields);
}
@@ -223,7 +238,7 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
}
private T newInstance() {
- if (!hasScalaDefaultValues) {
+ if (!hasDefaultValues) {
return newBean();
}
return Platform.newInstance(type);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
b/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
index 114a569b6..f540d333a 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/util/DefaultValueUtils.java
@@ -105,7 +105,7 @@ public class DefaultValueUtils {
* @param descriptors list of descriptors that are present in the
serialized data
* @return array of DefaultValueField objects
*/
- public DefaultValueField[] buildDefaultValueFields(
+ public final DefaultValueField[] buildDefaultValueFields(
Fory fory, Class<?> type,
java.util.List<org.apache.fory.type.Descriptor> descriptors) {
DefaultValueField[] defaultFieldsArray =
defaultValueFieldsCache.getIfPresent(type);
if (defaultFieldsArray != null) {
diff --git
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 02f5ab3ce..c64bb3891 100644
---
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -437,6 +437,9 @@
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
org.apache.fory.type.Type,\
org.apache.fory.type.Types,\
org.apache.fory.type.TypeUtils,\
+ org.apache.fory.util.DefaultValueUtils,\
+ org.apache.fory.util.DefaultValueUtils$DefaultValueField,\
+ org.apache.fory.util.DefaultValueUtils$ScalaDefaultValueSupport,\
org.apache.fory.util.ClassLoaderUtils$ByteArrayClassLoader,\
org.apache.fory.util.ClassLoaderUtils$ParentClassLoader,\
org.apache.fory.util.ClassLoaderUtils,\
diff --git a/kotlin/README.md b/kotlin/README.md
index ea685c204..24e6ff070 100644
--- a/kotlin/README.md
+++ b/kotlin/README.md
@@ -60,6 +60,76 @@ fun main(args: Array<String>) {
}
```
+## Default Value Support
+
+Fory Kotlin provides support for Kotlin data class default values during
serialization and deserialization. This feature allows for backward and forward
compatibility when data class schemas evolve.
+
+### How It Works
+
+When a Kotlin data class has parameters with default values, Fory can:
+
+1. **Detect default values** using Kotlin reflection
+2. **Apply default values** during deserialization when fields are missing
from serialized data
+3. **Support schema evolution** by allowing new fields with defaults to be
added without breaking existing serialized data
+
+### Example Usage
+
+```kotlin
+import org.apache.fory.Fory
+import org.apache.fory.config.CompatibleMode
+import org.apache.fory.serializer.kotlin.KotlinSerializers
+
+// Original data class
+data class User(val name: String, val age: Int)
+
+// Evolved data class with new field and default value
+data class UserV2(val name: String, val age: Int, val email: String =
"[email protected]")
+
+fun main() {
+ val fory = Fory.builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .build()
+ KotlinSerializers.registerSerializers(fory)
+ fory.register(User::class.java)
+ fory.register(UserV2::class.java)
+
+ // Serialize with old schema
+ val oldUser = User("John", 30)
+ val serialized = fory.serialize(oldUser)
+
+ // Deserialize with new schema - missing field gets default value
+ val newUser = fory.deserialize(serialized, UserV2::class.java)
+ println(newUser) // UserV2(name=John, age=30, [email protected])
+}
+```
+
+### Supported Default Value Types
+
+The following types are supported for default values:
+
+- **Primitive types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`,
`Short`, `Char`
+- **String**: `String`
+- **Collections**: `List`, `Set`, `Map` (with default instances)
+- **Custom objects**: Any object that can be instantiated via reflection
+
+### Configuration
+
+To enable default value support:
+
+1. **Enable compatible mode** (recommended for schema evolution):
+
+ ```kotlin
+ val fory = Fory.builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .build()
+ ```
+
+2. **Register Kotlin serializers**:
+
+ ```kotlin
+ KotlinSerializers.registerSerializers(fory)
+ ```
+
## Building Fory Kotlin
```bash
diff --git a/kotlin/pom.xml b/kotlin/pom.xml
index e8b3ba489..ab64f2e9f 100644
--- a/kotlin/pom.xml
+++ b/kotlin/pom.xml
@@ -219,5 +219,10 @@
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-reflect</artifactId>
+ <version>${kotlin.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git
a/kotlin/src/main/java/org/apache/fory/serializer/kotlin/KotlinSerializers.java
b/kotlin/src/main/java/org/apache/fory/serializer/kotlin/KotlinSerializers.java
index 5160e56c2..f5db4183f 100644
---
a/kotlin/src/main/java/org/apache/fory/serializer/kotlin/KotlinSerializers.java
+++
b/kotlin/src/main/java/org/apache/fory/serializer/kotlin/KotlinSerializers.java
@@ -35,6 +35,7 @@ import org.apache.fory.ThreadSafeFory;
import org.apache.fory.resolver.ClassResolver;
import org.apache.fory.serializer.collection.CollectionSerializers;
import org.apache.fory.serializer.collection.MapSerializers;
+import org.apache.fory.util.DefaultValueUtils;
/** KotlinSerializers provide default serializers for kotlin. */
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -46,6 +47,7 @@ public class KotlinSerializers {
}
public static void registerSerializers(Fory fory) {
+ DefaultValueUtils.setKotlinDefaultValueSupport(new
KotlinDefaultValueSupport());
ClassResolver resolver = fory.getClassResolver();
// UByte
diff --git
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/KotlinDefaultValueSupport.kt
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/KotlinDefaultValueSupport.kt
new file mode 100644
index 000000000..64ca7a5bc
--- /dev/null
+++
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/KotlinDefaultValueSupport.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.serializer.kotlin
+
+import java.lang.reflect.Type
+import kotlin.reflect.KParameter
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.jvm.javaType
+import org.apache.fory.logging.Logger
+import org.apache.fory.logging.LoggerFactory
+import org.apache.fory.memory.Platform
+import org.apache.fory.util.DefaultValueUtils
+
+/**
+ * Kotlin implementation of DefaultValueSupport for extracting default values
from Kotlin data
+ * classes.
+ *
+ * This class uses Kotlin native reflection to analyze data classes and
extract default values from
+ * their primary constructor parameters.
+ */
+internal class KotlinDefaultValueSupport :
DefaultValueUtils.DefaultValueSupport() {
+ private val LOG: Logger =
LoggerFactory.getLogger(KotlinDefaultValueSupport::class.java)
+
+ override fun hasDefaultValues(cls: Class<*>): Boolean {
+ return try {
+ if (!isKotlinDataClass(cls)) {
+ return false
+ }
+ getAllDefaultValues(cls).isNotEmpty()
+ } catch (e: Exception) {
+ LOG.warn("Error checking default values for class ${cls.name}:
${e.message}")
+ false
+ }
+ }
+
+ override fun getAllDefaultValues(cls: Class<*>): Map<String, Any> {
+ return try {
+ if (!isKotlinDataClass(cls)) {
+ return emptyMap()
+ }
+
+ val kClass = cls.kotlin
+ val primaryConstructor = kClass.primaryConstructor ?: return emptyMap()
+ val parameters = primaryConstructor.parameters
+ val argsMap = mutableMapOf<KParameter, Any>()
+ // Provide default values for all required (non-optional) parameters
+ for (parameter in parameters) {
+ if (!parameter.isOptional) {
+ val defaultValue = getDefaultValueForType(parameter.type.javaType)
+ if (defaultValue != null) {
+ argsMap[parameter] = defaultValue
+ } else {
+ // If we can't provide a default for a required parameter, we
can't get any defaults
+ return emptyMap()
+ }
+ }
+ }
+ // Create a single instance
+ val instance = primaryConstructor.callBy(argsMap)
+ val defaultValues = mutableMapOf<String, Any>()
+ // For each optional parameter, extract its value from the instance
+ for (parameter in parameters) {
+ if (parameter.isOptional && parameter.name != null) {
+ val property = kClass.memberProperties.find { it.name ==
parameter.name }
+ property?.let { prop ->
+ @Suppress("UNCHECKED_CAST")
+ val value = (prop as kotlin.reflect.KProperty1<Any,
*>).get(instance as Any)
+ if (value != null) {
+ defaultValues[parameter.name!!] = value
+ }
+ }
+ }
+ }
+ defaultValues
+ } catch (e: Exception) {
+ LOG.info("Error getting default values for class ${cls.name}:
${e.message}")
+ emptyMap()
+ }
+ }
+
+ override fun getDefaultValue(cls: Class<*>, fieldName: String): Any? {
+ return try {
+ if (!isKotlinDataClass(cls)) {
+ return null
+ }
+ getAllDefaultValues(cls)[fieldName]
+ } catch (e: Exception) {
+ LOG.info(
+ "Error getting default value for field $fieldName in class
${cls.name}: ${e.message}"
+ )
+ null
+ }
+ }
+
+ private fun isKotlinDataClass(cls: Class<*>): Boolean {
+ return try {
+ cls.kotlin.isData
+ } catch (e: Exception) {
+ LOG.info("Error checking if class ${cls.name} is a Kotlin data class:
${e.message}")
+ false
+ }
+ }
+
+ private fun getDefaultValueForType(type: Type): Any? {
+ val clazz =
+ when (type) {
+ is Class<*> -> type
+ is java.lang.reflect.ParameterizedType -> type.rawType as? Class<*>
+ else -> null
+ } ?: return null
+
+ return when (clazz) {
+ Int::class.java,
+ Int::class.javaPrimitiveType -> 0
+ Long::class.java,
+ Long::class.javaPrimitiveType -> 0L
+ Double::class.java,
+ Double::class.javaPrimitiveType -> 0.0
+ Float::class.java,
+ Float::class.javaPrimitiveType -> 0.0f
+ Boolean::class.java,
+ Boolean::class.javaPrimitiveType -> false
+ Byte::class.java,
+ Byte::class.javaPrimitiveType -> 0.toByte()
+ Short::class.java,
+ Short::class.javaPrimitiveType -> 0.toShort()
+ Char::class.java,
+ Char::class.javaPrimitiveType -> '\u0000'
+ String::class.java -> ""
+ else -> Platform.newInstance(clazz)
+ }
+ }
+}
diff --git
a/kotlin/src/test/kotlin/org/apache/fory/serializer/kotlin/DefaultValueTest.kt
b/kotlin/src/test/kotlin/org/apache/fory/serializer/kotlin/DefaultValueTest.kt
new file mode 100644
index 000000000..297c76525
--- /dev/null
+++
b/kotlin/src/test/kotlin/org/apache/fory/serializer/kotlin/DefaultValueTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.serializer.kotlin
+
+import org.apache.fory.Fory
+import org.apache.fory.config.CompatibleMode
+import org.testng.Assert.*
+import org.testng.annotations.Test
+
+// Test classes WITHOUT default values (for serialization)
+data class ClassNoDefaults(val v: String) // No x field at all
+
+data class ClassMultipleDefaultsNoDefaults(val v: String) // No x and y fields
at all
+
+data class ClassComplexDefaultsNoDefaults(val v: String) // No list field at
all
+
+// Test classes WITH default values (for deserialization)
+data class ClassWithDefaults(val v: String, val x: Int = 1)
+
+data class ClassMultipleDefaultsWithDefaults(val v: String, val x: Int = 1,
val y: Double = 2.0)
+
+data class ClassComplexDefaultsWithDefaults(val v: String, val list: List<Int>
= listOf(1, 2, 3))
+
+class DefaultValueTest {
+
+ private val support = KotlinDefaultValueSupport()
+
+ @Test
+ fun testHasDefaultValues() {
+ // Test classes with default values
+ assertTrue(support.hasDefaultValues(ClassWithDefaults::class.java))
+
assertTrue(support.hasDefaultValues(ClassMultipleDefaultsWithDefaults::class.java))
+
assertTrue(support.hasDefaultValues(ClassComplexDefaultsWithDefaults::class.java))
+
+ // Test classes without default values
+ assertFalse(support.hasDefaultValues(ClassNoDefaults::class.java))
+
assertFalse(support.hasDefaultValues(ClassMultipleDefaultsNoDefaults::class.java))
+
assertFalse(support.hasDefaultValues(ClassComplexDefaultsNoDefaults::class.java))
+
+ // Test non-data classes
+ assertFalse(support.hasDefaultValues(String::class.java))
+ assertFalse(support.hasDefaultValues(Int::class.java))
+ assertFalse(support.hasDefaultValues(List::class.java))
+ }
+
+ @Test
+ fun testGetAllDefaultValues() {
+ // Test single default value
+ val singleDefaults =
support.getAllDefaultValues(ClassWithDefaults::class.java)
+ assertEquals(1, singleDefaults.size)
+ assertEquals(1, singleDefaults["x"])
+
+ // Test multiple default values
+ val multipleDefaults =
+
support.getAllDefaultValues(ClassMultipleDefaultsWithDefaults::class.java)
+ assertEquals(2, multipleDefaults.size)
+ assertEquals(1, multipleDefaults["x"])
+ assertEquals(2.0, multipleDefaults["y"])
+
+ // Test complex default values
+ val complexDefaults =
support.getAllDefaultValues(ClassComplexDefaultsWithDefaults::class.java)
+ assertEquals(1, complexDefaults.size)
+ assertEquals(listOf(1, 2, 3), complexDefaults["list"])
+
+ // Test classes without default values
+ val noDefaults = support.getAllDefaultValues(ClassNoDefaults::class.java)
+ assertTrue(noDefaults.isEmpty())
+ }
+
+ @Test
+ fun testGetDefaultValue() {
+ // Test existing default values
+ assertEquals(1, support.getDefaultValue(ClassWithDefaults::class.java,
"x"))
+ assertEquals(1,
support.getDefaultValue(ClassMultipleDefaultsWithDefaults::class.java, "x"))
+ assertEquals(2.0,
support.getDefaultValue(ClassMultipleDefaultsWithDefaults::class.java, "y"))
+ assertEquals(
+ listOf(1, 2, 3),
+ support.getDefaultValue(ClassComplexDefaultsWithDefaults::class.java,
"list")
+ )
+
+ // Test non-existent fields
+ assertNull(support.getDefaultValue(ClassWithDefaults::class.java,
"nonExistent"))
+ assertNull(support.getDefaultValue(ClassNoDefaults::class.java, "x"))
+
+ // Test fields without default values
+ assertNull(support.getDefaultValue(ClassWithDefaults::class.java, "v"))
+ }
+
+ @Test
+ fun testNonDataClasses() {
+ // Test regular classes
+ assertFalse(support.hasDefaultValues(RegularClass::class.java))
+ assertTrue(support.getAllDefaultValues(RegularClass::class.java).isEmpty())
+ assertNull(support.getDefaultValue(RegularClass::class.java, "field"))
+
+ // Test interfaces
+ assertFalse(support.hasDefaultValues(TestInterface::class.java))
+
assertTrue(support.getAllDefaultValues(TestInterface::class.java).isEmpty())
+ assertNull(support.getDefaultValue(TestInterface::class.java, "field"))
+
+ // Test enums
+ assertFalse(support.hasDefaultValues(TestEnum::class.java))
+ assertTrue(support.getAllDefaultValues(TestEnum::class.java).isEmpty())
+ assertNull(support.getDefaultValue(TestEnum::class.java, "field"))
+ }
+
+ @Test
+ fun testDefaultValueDeserialization() {
+ val fory =
+ Fory.builder()
+ .requireClassRegistration(false)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .build()
+ KotlinSerializers.registerSerializers(fory)
+ val obj = ClassNoDefaults("test")
+ val serialized = fory.serialize(obj)
+ val deserialized = fory.deserialize(serialized,
ClassWithDefaults::class.java)
+ assertEquals(deserialized.v, obj.v)
+ assertEquals(deserialized.x, 1)
+
+ val obj2 = ClassMultipleDefaultsNoDefaults("test")
+ val serialized2 = fory.serialize(obj2)
+ val deserialized2 = fory.deserialize(serialized2,
ClassMultipleDefaultsWithDefaults::class.java)
+ assertEquals(deserialized2.v, obj2.v)
+ assertEquals(deserialized2.x, 1)
+ assertEquals(deserialized2.y, 2.0)
+
+ val obj3 = ClassComplexDefaultsNoDefaults("test")
+ val serialized3 = fory.serialize(obj3)
+ val deserialized3 = fory.deserialize(serialized3,
ClassComplexDefaultsWithDefaults::class.java)
+ assertEquals(deserialized3.v, obj3.v)
+ assertEquals(deserialized3.list, listOf(1, 2, 3))
+ }
+}
+
+// Additional test classes
+class RegularClass(val field: String)
+
+interface TestInterface {
+ val field: String
+}
+
+enum class TestEnum {
+ VALUE1,
+ VALUE2
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]