This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch releases-0.10 in repository https://gitbox.apache.org/repos/asf/fury.git
commit 0c18f71ff7bb197259c09080cd7c00e423201f0a Author: orisgarno <[email protected]> AuthorDate: Sat Feb 1 21:38:51 2025 +0700 feat(java): deserialize one pojo into another type (#2012) <!-- **Thanks for contributing to Fury.** **If this is your first time opening a PR on fury, you can refer to [CONTRIBUTING.md](https://github.com/apache/fury/blob/main/CONTRIBUTING.md).** Contribution Checklist - The **Apache Fury (incubating)** community has restrictions on the naming of pr titles. You can also find instructions in [CONTRIBUTING.md](https://github.com/apache/fury/blob/main/CONTRIBUTING.md). - Fury has a strong focus on performance. If the PR you submit will have an impact on performance, please benchmark it first and provide the benchmark result here. --> replace class def if target class is different type with the actual serialized one, so it can be deserialized to another type <!-- Describe the purpose of this PR. --> <!-- Is there any related issue? Please attach here. - #xxxx0 - #xxxx1 - #xxxx2 --> <!-- If any user-facing interface changes, please [open an issue](https://github.com/apache/fury/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? <!-- 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. --> --------- Co-authored-by: chaokunyang <[email protected]> --- docs/guide/java_serialization_guide.md | 4 +--- .../src/main/java/org/apache/fury/Fury.java | 2 +- .../main/java/org/apache/fury/meta/ClassDef.java | 18 ++++++++++++++++ .../java/org/apache/fury/meta/ClassDefEncoder.java | 10 ++++++++- .../org/apache/fury/resolver/ClassResolver.java | 25 ++++++++++++++++++++++ .../DifferentPOJOCompatibleSerializerTest.java | 25 +++++----------------- ...OCompatibleSerializerWithRegistrationTest.java} | 5 ++--- 7 files changed, 61 insertions(+), 28 deletions(-) diff --git a/docs/guide/java_serialization_guide.md b/docs/guide/java_serialization_guide.md index 70764393..9a792d7b 100644 --- a/docs/guide/java_serialization_guide.md +++ b/docs/guide/java_serialization_guide.md @@ -521,9 +521,7 @@ consistent between serialization and deserialization. ### Deserialize POJO into another type Fury allows you to serialize one POJO and deserialize it into a different POJO. To achieve this, configure Fury with -`CompatibleMode` set to `org.apache.fury.config.CompatibleMode.COMPATIBLE`. Additionally, you only need to register the -specific classes you want to serialize or deserialize to setup type mapping relationship; there's no need to register any nested classes within them. -[See example here](/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java) +`CompatibleMode` set to `org.apache.fury.config.CompatibleMode.COMPATIBLE`. ### Use wrong API for deserialization diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java b/java/fury-core/src/main/java/org/apache/fury/Fury.java index 94534709..939340fe 100644 --- a/java/fury-core/src/main/java/org/apache/fury/Fury.java +++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java @@ -1126,7 +1126,7 @@ public final class Fury implements BaseFury { if (nextReadRefId >= NOT_NULL_VALUE_FLAG) { ClassInfo classInfo; if (shareMeta) { - classInfo = classResolver.readClassInfo(buffer); + classInfo = classResolver.readClassInfoWithMetaShare(buffer, cls); } else { classInfo = classResolver.getClassInfo(cls); } diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java index 1b6177e0..695d552b 100644 --- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java +++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.SortedMap; +import java.util.stream.Collectors; import org.apache.fury.Fury; import org.apache.fury.builder.MetaSharedCodecBuilder; import org.apache.fury.collection.Tuple2; @@ -787,4 +788,21 @@ public class ClassDef implements Serializable { ClassResolver classResolver, Class<?> type, List<Field> fields, boolean isObjectType) { return ClassDefEncoder.buildClassDef(classResolver, type, fields, isObjectType); } + + public ClassDef replaceRootClassTo(ClassResolver classResolver, Class<?> targetCls) { + String name = targetCls.getName(); + List<FieldInfo> fieldInfos = + fieldsInfo.stream() + .map( + fieldInfo -> { + if (fieldInfo.definedClass.equals(classSpec.entireClassName)) { + return new FieldInfo(name, fieldInfo.fieldName, fieldInfo.fieldType); + } else { + return fieldInfo; + } + }) + .collect(Collectors.toList()); + return ClassDefEncoder.buildClassDefWithFieldInfos( + classResolver, targetCls, fieldInfos, isObjectType); + } } diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java index c1ac5ebd..d40dd53d 100644 --- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java +++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java @@ -105,7 +105,15 @@ class ClassDefEncoder { /** Build class definition from fields of class. */ static ClassDef buildClassDef( ClassResolver classResolver, Class<?> type, List<Field> fields, boolean isObjectType) { - List<FieldInfo> fieldInfos = buildFieldsInfo(classResolver, fields); + return buildClassDefWithFieldInfos( + classResolver, type, buildFieldsInfo(classResolver, fields), isObjectType); + } + + static ClassDef buildClassDefWithFieldInfos( + ClassResolver classResolver, + Class<?> type, + List<ClassDef.FieldInfo> fieldInfos, + boolean isObjectType) { Map<String, List<FieldInfo>> classLayers = getClassFields(type, fieldInfos); fieldInfos = new ArrayList<>(fieldInfos.size()); classLayers.values().forEach(fieldInfos::addAll); diff --git a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java index dbaf4bb4..840f3285 100644 --- a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java +++ b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java @@ -251,6 +251,8 @@ public class ClassResolver { private final Map<Class<?>, FieldResolver> fieldResolverMap = new HashMap<>(); private final LongMap<Tuple2<ClassDef, ClassInfo>> classIdToDef = new LongMap<>(); private final Map<Class<?>, ClassDef> currentLayerClassDef = new HashMap<>(); + // Tuple2<Class, Class>: Tuple2<From Class, To Class> + private final Map<Tuple2<Class<?>, Class<?>>, ClassInfo> transformedClassInfo = new HashMap<>(); // TODO(chaokunyang) Better to use soft reference, see ObjectStreamClass. private final ConcurrentHashMap<Tuple2<Class<?>, Boolean>, SortedMap<Field, Descriptor>> descriptorsCache = new ConcurrentHashMap<>(); @@ -1408,6 +1410,28 @@ public class ClassResolver { return classInfo; } + + public ClassInfo readClassInfoWithMetaShare(MemoryBuffer buffer, Class<?> targetClass) { + assert metaContextShareEnabled; + ClassInfo classInfo = + readClassInfoWithMetaShare(buffer, fury.getSerializationContext().getMetaContext()); + Class<?> readClass = classInfo.getCls(); + // replace target class if needed + if (targetClass != readClass) { + Tuple2<Class<?>, Class<?>> key = Tuple2.of(readClass, targetClass); + ClassInfo newClassInfo = extRegistry.transformedClassInfo.get(key); + if (newClassInfo == null) { + // similar to create serializer for `NonexistentMetaShared` + newClassInfo = + getMetaSharedClassInfo( + classInfo.classDef.replaceRootClassTo(this, targetClass), targetClass); + extRegistry.transformedClassInfo.put(key, newClassInfo); + } + return newClassInfo; + } + return classInfo; + } + private ClassInfo buildMetaSharedClassInfo( Tuple2<ClassDef, ClassInfo> classDefTuple, ClassDef classDef) { ClassInfo classInfo; @@ -1436,6 +1460,7 @@ public class ClassResolver { Short classId = extRegistry.registeredClassIdMap.get(cls); ClassInfo classInfo = new ClassInfo(this, cls, null, null, classId == null ? NO_CLASS_ID : classId); + classInfo.classDef = classDef; if (NonexistentClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) { if (cls == NonexistentMetaShared.class) { classInfo.setSerializer(this, new NonexistentClassSerializer(fury, classDef)); diff --git a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java index 3a7436d9..59dd15d4 100644 --- a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java @@ -29,8 +29,7 @@ import org.testng.annotations.Test; /** * Test COMPATIBILITY mode that supports - same field type and name can be deserialized to other - * class with different name - scrambled field order to make sure it could handle different field - * order - missing or extra field from source class to target class - generic class + * class with different name */ public class DifferentPOJOCompatibleSerializerTest extends Assert { @@ -61,9 +60,9 @@ public class DifferentPOJOCompatibleSerializerTest extends Assert { ClassCompleteField<String> subclass = new ClassCompleteField<>("subclass", "subclass2"); ClassCompleteField<ClassCompleteField<String>> classCompleteField = new ClassCompleteField<>(subclass, subclass); - byte[] serialized = getFury(ClassCompleteField.class).serializeJavaObject(classCompleteField); + byte[] serialized = getFury().serializeJavaObject(classCompleteField); ClassMissingField<ClassMissingField<String>> classMissingField = - getFury(ClassMissingField.class).deserializeJavaObject(serialized, ClassMissingField.class); + getFury().deserializeJavaObject(serialized, ClassMissingField.class); assertEq(classCompleteField, classMissingField); } @@ -73,29 +72,15 @@ public class DifferentPOJOCompatibleSerializerTest extends Assert { ClassMissingField<String> subclass = new ClassMissingField<>("subclass"); ClassMissingField classMissingField = new ClassMissingField(subclass); - byte[] serialized = getFury(ClassMissingField.class).serializeJavaObject(classMissingField); + byte[] serialized = getFury().serializeJavaObject(classMissingField); ClassCompleteField classCompleteField = - getFury(ClassCompleteField.class) - .deserializeJavaObject(serialized, ClassCompleteField.class); + getFury().deserializeJavaObject(serialized, ClassCompleteField.class); assertEq(classCompleteField, classMissingField); } void assertEq(ClassCompleteField classCompleteField, ClassMissingField classMissingField) { - assertEqSubClass( - (ClassCompleteField) classCompleteField.getPrivateFieldSubClass(), - (ClassMissingField) classMissingField.getPrivateFieldSubClass()); - assertEquals(classCompleteField.getPrivateMap(), classMissingField.getPrivateMap()); - assertEquals(classCompleteField.getPrivateList(), classMissingField.getPrivateList()); - assertEquals(classCompleteField.getPrivateString(), classMissingField.getPrivateString()); - assertEquals(classCompleteField.getPrivateInt(), classMissingField.getPrivateInt()); - } - - void assertEqSubClass( - ClassCompleteField classCompleteField, ClassMissingField classMissingField) { - assertEquals( - classCompleteField.getPrivateFieldSubClass(), classMissingField.getPrivateFieldSubClass()); assertEquals(classCompleteField.getPrivateMap(), classMissingField.getPrivateMap()); assertEquals(classCompleteField.getPrivateList(), classMissingField.getPrivateList()); assertEquals(classCompleteField.getPrivateString(), classMissingField.getPrivateString()); diff --git a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java similarity index 94% copy from java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java copy to java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java index 3a7436d9..c5c8d15e 100644 --- a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java @@ -29,10 +29,9 @@ import org.testng.annotations.Test; /** * Test COMPATIBILITY mode that supports - same field type and name can be deserialized to other - * class with different name - scrambled field order to make sure it could handle different field - * order - missing or extra field from source class to target class - generic class + * also test generic class */ -public class DifferentPOJOCompatibleSerializerTest extends Assert { +public class DifferentPOJOCompatibleSerializerWithRegistrationTest extends Assert { Fury getFury(Class<?>... classes) { Fury instance = --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
