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]

Reply via email to