Repository: alibaba/fastjson2
Pull request #3342 (2025-02-19 18:40:48)

Feat 3335
### What this PR does / why we need it?



### Summary of your change



#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.



Patch:
diff --git a/core/src/main/java/com/alibaba/fastjson2/annotation/JSONField.java b/core/src/main/java/com/alibaba/fastjson2/annotation/JSONField.java
index 1dc2cd0ca..a61d68b3c 100644
--- a/core/src/main/java/com/alibaba/fastjson2/annotation/JSONField.java
+++ b/core/src/main/java/com/alibaba/fastjson2/annotation/JSONField.java
@@ -121,4 +121,9 @@ public @interface JSONField {
      * @since 2.0.52
      */
     Class<?> arrayToMapDuplicateHandler() default Void.class;
+
+    /**
+     * @since 2.0.56
+     */
+    Class<?> contentAs() default Void.class;
 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/codec/FieldInfo.java b/core/src/main/java/com/alibaba/fastjson2/codec/FieldInfo.java
index dd433293d..34c37f06b 100644
--- a/core/src/main/java/com/alibaba/fastjson2/codec/FieldInfo.java
+++ b/core/src/main/java/com/alibaba/fastjson2/codec/FieldInfo.java
@@ -22,6 +22,7 @@ public class FieldInfo {
     public static final long DISABLE_JSONB = 1L << 60;
     public static final long BACKR_EFERENCE = 1L << 61;
     public static final long RECORD = 1L << 62;
+    public static final long CONTENT_AS = 1L << 63;
 
     public String fieldName;
     public String format;
@@ -46,6 +47,11 @@ public class FieldInfo {
     public String arrayToMapKey;
     public Class<?> arrayToMapDuplicateHandler;
 
+    /**
+     * @since 2.0.56
+     */
+    public Class<?> contentAs;
+
     public ObjectReader getInitReader() {
         Class<?> calzz = readUsing;
         if (calzz != null && ObjectReader.class.isAssignableFrom(calzz)) {
@@ -97,5 +103,6 @@ public class FieldInfo {
 
         arrayToMapKey = null;
         arrayToMapDuplicateHandler = null;
+        contentAs = null;
     }
 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
index 1cbb54d7a..4d245e704 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
@@ -1308,10 +1308,16 @@ public class ObjectReaderBaseModule
             if (!keyName.isEmpty()) {
                 fieldInfo.arrayToMapKey = keyName;
             }
+
             Class<?> arrayToMapDuplicateHandler = jsonField.arrayToMapDuplicateHandler();
             if (arrayToMapDuplicateHandler != Void.class) {
                 fieldInfo.arrayToMapDuplicateHandler = arrayToMapDuplicateHandler;
             }
+
+            Class<?> contentAs = jsonField.contentAs();
+            if (contentAs != Void.class) {
+                fieldInfo.contentAs = contentAs;
+            }
         }
     }
 
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterList.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterList.java
index 97226c47d..5cafa0d66 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterList.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterList.java
@@ -24,6 +24,7 @@ abstract class FieldWriterList<T>
     ObjectWriter listWriter;
     ObjectWriter itemObjectWriter;
     final boolean writeAsString;
+    final Class<?> contentAs;
 
     FieldWriterList(
             String name,
@@ -35,9 +36,11 @@ abstract class FieldWriterList<T>
             Type fieldType,
             Class fieldClass,
             Field field,
-            Method method
+            Method method,
+            Class<?> contentAs
     ) {
         super(name, ordinal, features, format, null, label, fieldType, fieldClass, field, method);
+        this.contentAs = contentAs;
 
         writeAsString = (features & WriteNonStringValueAsString.mask) != 0;
 
@@ -77,6 +80,13 @@ abstract class FieldWriterList<T>
 
     @Override
     public ObjectWriter getItemWriter(JSONWriter jsonWriter, Type itemType) {
+        if (contentAs != null) {
+            ObjectWriter itemObjectWriter = this.itemObjectWriter;
+            if (itemObjectWriter != null) {
+                return itemObjectWriter;
+            }
+            return this.itemObjectWriter = jsonWriter.getObjectWriter(this.contentAs, contentAs);
+        }
         if (itemType == null || itemType == this.itemType) {
             if (itemObjectWriter != null) {
                 return itemObjectWriter;
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListField.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListField.java
index 35032ec51..128a6134a 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListField.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListField.java
@@ -19,9 +19,10 @@ final class FieldWriterListField<T>
             String label,
             Type fieldType,
             Class fieldClass,
-            Field field
+            Field field,
+            Class<?> contentAs
     ) {
-        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, null);
+        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, null, contentAs);
     }
 
     @Override
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListFunc.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListFunc.java
index 045e3a082..540d2b5f5 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListFunc.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListFunc.java
@@ -25,9 +25,10 @@ final class FieldWriterListFunc<T>
             Method method,
             Function<T, List> function,
             Type fieldType,
-            Class fieldClass
+            Class fieldClass,
+            Class<?> contentAs
     ) {
-        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, method);
+        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, method, contentAs);
         this.function = function;
     }
 
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListMethod.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListMethod.java
index 9b1703a6d..ee4829e93 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListMethod.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterListMethod.java
@@ -23,9 +23,10 @@ final class FieldWriterListMethod<T>
             Field field,
             Method method,
             Type fieldType,
-            Class fieldClass
+            Class fieldClass,
+            Class<?> contentAs
     ) {
-        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, method);
+        super(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, method, contentAs);
     }
 
     @Override
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMap.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMap.java
new file mode 100644
index 000000000..dba19ba7c
--- /dev/null
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMap.java
@@ -0,0 +1,88 @@
+package com.alibaba.fastjson2.writer;
+
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.codec.FieldInfo;
+import com.alibaba.fastjson2.util.ParameterizedTypeImpl;
+import com.alibaba.fastjson2.util.TypeUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+abstract class FieldWriterMap
+        extends FieldWriterObject {
+    protected final Class<?> contentAs;
+    protected Type contentAsFieldType;
+    volatile ObjectWriter mapWriter;
+    private final Type keyType;
+    private final Type valueType;
+    final boolean valueTypeRefDetect;
+    volatile ObjectWriter valueWriter;
+
+    protected FieldWriterMap(
+            String name,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Type fieldType,
+            Class fieldClass,
+            Field field,
+            Method method,
+            Class<?> contentAs
+    ) {
+        super(name, ordinal, features, format, locale, label, fieldType, fieldClass, field, method);
+        Type keyType = null, valueType = null;
+        Type contentAsFieldType = null;
+        if (fieldType instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType) fieldType;
+            Type[] actualTypeArguments = pt.getActualTypeArguments();
+            if (actualTypeArguments.length == 2) {
+                keyType = actualTypeArguments[0];
+                valueType = actualTypeArguments[1];
+            }
+        }
+        if (keyType == null) {
+            keyType = Object.class;
+        }
+        if (valueType == null) {
+            valueType = Object.class;
+        }
+        if (contentAs != null) {
+            contentAsFieldType = new ParameterizedTypeImpl(fieldClass, String.class, contentAs);
+        }
+        this.contentAs = contentAs;
+        this.contentAsFieldType = contentAsFieldType;
+        this.keyType = keyType;
+        this.valueType = valueType;
+        this.valueTypeRefDetect = !ObjectWriterProvider.isNotReferenceDetect(TypeUtils.getClass(valueType));
+    }
+
+    @Override
+    public ObjectWriter getObjectWriter(JSONWriter jsonWriter, Class valueClass) {
+        Class<?> contentAs = this.contentAs;
+        if (contentAs == null || !fieldClass.isAssignableFrom(valueClass)) {
+            return super.getObjectWriter(jsonWriter, valueClass);
+        }
+
+        ObjectWriter valueWriter = this.valueWriter;
+        if (valueWriter != null) {
+            return valueWriter;
+        }
+
+        Type fieldType = this.fieldType;
+        Type valueType = this.valueType;
+        long features = this.features;
+        if (contentAs != null) {
+            valueType = contentAs;
+            fieldType = contentAsFieldType;
+            features |= FieldInfo.CONTENT_AS;
+        }
+        valueWriter = new ObjectWriterImplMap(keyType, valueType, format, valueClass, fieldType, features);
+        this.mapWriter = valueWriter;
+        return valueWriter;
+    }
+}
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapField.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapField.java
new file mode 100644
index 000000000..0a0e2fc98
--- /dev/null
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapField.java
@@ -0,0 +1,25 @@
+package com.alibaba.fastjson2.writer;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+final class FieldWriterMapField
+        extends FieldWriterMap {
+    FieldWriterMapField(
+            String name,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Type fieldType,
+            Class fieldClass,
+            Field field,
+            Method method,
+            Class<?> contentAs
+    ) {
+        super(name, ordinal, features, format, locale, label, fieldType, fieldClass, field, method, contentAs);
+    }
+}
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapFunction.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapFunction.java
new file mode 100644
index 000000000..9a94bf703
--- /dev/null
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapFunction.java
@@ -0,0 +1,39 @@
+package com.alibaba.fastjson2.writer;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Locale;
+import java.util.function.Function;
+
+final class FieldWriterMapFunction
+        extends FieldWriterMap {
+    final Function function;
+    FieldWriterMapFunction(
+            String name,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Type fieldType,
+            Class fieldClass,
+            Field field,
+            Method method,
+            Function function,
+            Class<?> contentAs
+    ) {
+        super(name, ordinal, features, format, locale, label, fieldType, fieldClass, field, method, contentAs);
+        this.function = function;
+    }
+
+    @Override
+    public Object getFieldValue(Object object) {
+        return function.apply(object);
+    }
+
+    @Override
+    public Function getFunction() {
+        return function;
+    }
+}
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapMethod.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapMethod.java
new file mode 100644
index 000000000..e3cfd3926
--- /dev/null
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterMapMethod.java
@@ -0,0 +1,37 @@
+package com.alibaba.fastjson2.writer;
+
+import com.alibaba.fastjson2.JSONException;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+final class FieldWriterMapMethod
+        extends FieldWriterMap {
+    FieldWriterMapMethod(
+            String name,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Type fieldType,
+            Class fieldClass,
+            Field field,
+            Method method,
+            Class<?> contentAs
+    ) {
+        super(name, ordinal, features, format, locale, label, fieldType, fieldClass, field, method, contentAs);
+    }
+
+    @Override
+    public Object getFieldValue(Object object) {
+        try {
+            return method.invoke(object);
+        } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+            throw new JSONException("invoke getter method error, " + fieldName, e);
+        }
+    }
+}
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java
index 4d02e5a25..3c181c02a 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java
@@ -97,7 +97,7 @@ public class FieldWriterObject<T>
         }
     }
 
-    private ObjectWriter getObjectWriterVoid(JSONWriter jsonWriter, Class valueClass) {
+    protected final ObjectWriter getObjectWriterVoid(JSONWriter jsonWriter, Class valueClass) {
         ObjectWriter formattedWriter = null;
         if (BeanUtils.isExtendedMap(valueClass) && SUPER.equals(fieldName)) {
             JSONWriter.Context context = jsonWriter.context;
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java
index 55b45fbbe..cb562f18b 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java
@@ -541,6 +541,12 @@ public class ObjectWriterBaseModule
                                 fieldInfo.valueUsing = valueUsing;
                             }
                             break;
+                        case "contentAs":
+                            Class<?> contentAs = (Class) result;
+                            if (contentAs != Void.class) {
+                                fieldInfo.contentAs = contentAs;
+                            }
+                            break;
                         default:
                             break;
                     }
@@ -967,6 +973,11 @@ public class ObjectWriterBaseModule
             if (ObjectWriter.class.isAssignableFrom(serializeUsing)) {
                 fieldInfo.writeUsing = serializeUsing;
             }
+
+            Class contentAs = jsonField.contentAs();
+            if (contentAs != Void.class) {
+                fieldInfo.contentAs = contentAs;
+            }
         }
 
         /**
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java
index b6c5ffade..1eff38c39 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java
@@ -258,7 +258,17 @@ public class ObjectWriterCreator {
             format = beanInfo.format;
         }
 
-        return createFieldWriter(provider, fieldName, fieldInfo.ordinal, fieldInfo.features, format, fieldInfo.locale, fieldInfo.label, field, writeUsingWriter);
+        return createFieldWriter(
+                provider,
+                fieldName,
+                fieldInfo.ordinal,
+                fieldInfo.features,
+                format,
+                fieldInfo.locale,
+                fieldInfo.label,
+                field,
+                writeUsingWriter,
+                fieldInfo.contentAs);
     }
 
     public ObjectWriter createObjectWriter(
@@ -456,7 +466,8 @@ public class ObjectWriterCreator {
                                     fieldInfo.format,
                                     fieldInfo.label,
                                     method,
-                                    writeUsingWriter
+                                    writeUsingWriter,
+                                    fieldInfo.contentAs
                             );
                         } catch (Throwable ignored) {
                             jitErrorCount.incrementAndGet();
@@ -471,9 +482,11 @@ public class ObjectWriterCreator {
                                 fieldInfo.ordinal,
                                 fieldInfo.features,
                                 fieldInfo.format,
+                                fieldInfo.locale,
                                 fieldInfo.label,
                                 method,
-                                writeUsingWriter
+                                writeUsingWriter,
+                                fieldInfo.contentAs
                         );
                     }
 
@@ -732,6 +745,32 @@ public class ObjectWriterCreator {
             String label,
             Field field,
             ObjectWriter initObjectWriter
+    ) {
+        return createFieldWriter(
+                provider,
+                fieldName,
+                ordinal,
+                features,
+                format,
+                locale,
+                label,
+                field,
+                initObjectWriter,
+                null
+        );
+    }
+
+    public <T> FieldWriter<T> createFieldWriter(
+            ObjectWriterProvider provider,
+            String fieldName,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Field field,
+            ObjectWriter initObjectWriter,
+            Class<?> contentAs
     ) {
         Class<?> declaringClass = field.getDeclaringClass();
         Method method = null;
@@ -859,7 +898,11 @@ public class ObjectWriterCreator {
             if (fieldType instanceof ParameterizedType) {
                 itemType = ((ParameterizedType) fieldType).getActualTypeArguments()[0];
             }
-            return new FieldWriterListField(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field);
+            return new FieldWriterListField(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, contentAs);
+        }
+
+        if (Map.class.isAssignableFrom(fieldClass)) {
+            return new FieldWriterMapField(fieldName, ordinal, features, format, locale, label, field.getGenericType(), fieldClass, field, null, contentAs);
         }
 
         if (fieldClass.isArray() && !fieldClass.getComponentType().isPrimitive()) {
@@ -867,7 +910,7 @@ public class ObjectWriterCreator {
             return new FieldWriterObjectArrayField(fieldName, itemClass, ordinal, features, format, label, itemClass, fieldClass, field);
         }
 
-        return new FieldWriterObject(fieldName, ordinal, features, format, null, label, field.getGenericType(), fieldClass, field, null);
+        return new FieldWriterObject(fieldName, ordinal, features, format, locale, label, field.getGenericType(), fieldClass, field, null);
     }
 
     public <T> FieldWriter<T> createFieldWriter(Class<T> objectType,
@@ -907,7 +950,8 @@ public class ObjectWriterCreator {
                 format,
                 null,
                 label,
-                method, initObjectWriter
+                method,
+                initObjectWriter
         );
     }
 
@@ -922,6 +966,22 @@ public class ObjectWriterCreator {
             String label,
             Method method,
             ObjectWriter initObjectWriter
+    ) {
+        return createFieldWriter(provider, objectType, fieldName, ordinal, features, format, locale, label, method, initObjectWriter, null);
+    }
+
+    public <T> FieldWriter<T> createFieldWriter(
+            ObjectWriterProvider provider,
+            Class<T> objectType,
+            String fieldName,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Method method,
+            ObjectWriter initObjectWriter,
+            Class<?> contentAs
     ) {
         method.setAccessible(true);
         Class<?> fieldClass = method.getReturnType();
@@ -1021,7 +1081,11 @@ public class ObjectWriterCreator {
             } else {
                 itemType = Object.class;
             }
-            return new FieldWriterListMethod(fieldName, itemType, ordinal, features, format, label, null, method, fieldType, fieldClass);
+            return new FieldWriterListMethod(fieldName, itemType, ordinal, features, format, label, null, method, fieldType, fieldClass, contentAs);
+        }
+
+        if (Map.class.isAssignableFrom(fieldClass)) {
+            return new FieldWriterMapMethod(fieldName, ordinal, features, format, locale, label, fieldType, fieldClass, null, method, contentAs);
         }
 
         if (fieldClass == Float[].class || fieldClass == Double[].class || fieldClass == BigDecimal[].class) {
@@ -1166,6 +1230,40 @@ public class ObjectWriterCreator {
             Field field,
             Method method,
             Function<T, V> function
+    ) {
+        return createFieldWriter(
+                provider,
+                objectClass,
+                fieldName,
+                ordinal,
+                features,
+                format,
+                locale,
+                label,
+                fieldType,
+                fieldClass,
+                field,
+                method,
+                function,
+                null
+        );
+    }
+
+    public <T, V> FieldWriter<T> createFieldWriter(
+            ObjectWriterProvider provider,
+            Class<T> objectClass,
+            String fieldName,
+            int ordinal,
+            long features,
+            String format,
+            Locale locale,
+            String label,
+            Type fieldType,
+            Class<V> fieldClass,
+            Field field,
+            Method method,
+            Function<T, V> function,
+            Class<?> contentAs
     ) {
         if (fieldClass == Byte.class) {
             return new FieldWriterInt8Func(fieldName, ordinal, features, format, label, field, method, function);
@@ -1249,9 +1347,13 @@ public class ObjectWriterCreator {
                     if (itemType == String.class) {
                         return new FieldWriterListStrFunc(fieldName, ordinal, features, format, label, field, method, function, fieldType, fieldClass);
                     }
-                    return new FieldWriterListFunc(fieldName, ordinal, features, format, label, itemType, field, method, function, fieldType, fieldClass);
+                    return new FieldWriterListFunc(fieldName, ordinal, features, format, label, itemType, field, method, function, fieldType, fieldClass, contentAs);
                 }
             }
+
+            if (rawType instanceof Class && Map.class.isAssignableFrom((Class) rawType)) {
+                return new FieldWriterMapFunction(fieldName, ordinal, features, format, locale, label, fieldType, fieldClass, field, method, function, contentAs);
+            }
         }
 
         if (Modifier.isFinal(fieldClass.getModifiers())) {
@@ -1368,7 +1470,8 @@ public class ObjectWriterCreator {
             String format,
             String label,
             Method method,
-            ObjectWriter initObjectWriter
+            ObjectWriter initObjectWriter,
+            Class<?> contentAs
     ) {
         Class<?> fieldClass = method.getReturnType();
         Type fieldType = method.getGenericReturnType();
@@ -1439,6 +1542,6 @@ public class ObjectWriterCreator {
         }
 
         Function function = (Function) lambda;
-        return createFieldWriter(provider, objectClass, fieldName, ordinal, features, format, label, fieldType, fieldClass, field, method, function);
+        return createFieldWriter(provider, objectClass, fieldName, ordinal, features, format, null, label, fieldType, fieldClass, field, method, function, contentAs);
     }
 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java
index 15b1218e0..c900f7ca1 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java
@@ -323,7 +323,8 @@ public class ObjectWriterCreatorASM
                                     fieldInfo.format,
                                     fieldInfo.label,
                                     method,
-                                    writeUsingWriter
+                                    writeUsingWriter,
+                                    fieldInfo.contentAs
                             );
                         } catch (Throwable e) {
                             jitErrorCount.incrementAndGet();
@@ -341,7 +342,8 @@ public class ObjectWriterCreatorASM
                                 fieldInfo.locale,
                                 fieldInfo.label,
                                 method,
-                                writeUsingWriter
+                                writeUsingWriter,
+                                fieldInfo.contentAs
                         );
                     }
 
@@ -4397,13 +4399,14 @@ public class ObjectWriterCreatorASM
             Locale locale,
             String label,
             Field field,
-            ObjectWriter initObjectWriter
+            ObjectWriter initObjectWriter,
+            Class<?> contentAs
     ) {
         Class<?> declaringClass = field.getDeclaringClass();
         if (Throwable.class.isAssignableFrom(declaringClass)
                 || declaringClass.getName().startsWith("java.lang")
         ) {
-            return super.createFieldWriter(provider, fieldName, ordinal, features, format, locale, label, field, initObjectWriter);
+            return super.createFieldWriter(provider, fieldName, ordinal, features, format, locale, label, field, initObjectWriter, contentAs);
         }
 
         Class<?> fieldClass = field.getType();
@@ -4535,7 +4538,11 @@ public class ObjectWriterCreatorASM
             if (fieldType instanceof ParameterizedType) {
                 itemType = ((ParameterizedType) fieldType).getActualTypeArguments()[0];
             }
-            return new FieldWriterListField(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field);
+            return new FieldWriterListField(fieldName, itemType, ordinal, features, format, label, fieldType, fieldClass, field, contentAs);
+        }
+
+        if (Map.class.isAssignableFrom(fieldClass)) {
+            return new FieldWriterMapField(fieldName, ordinal, features, format, locale, label, field.getGenericType(), fieldClass, field, null, contentAs);
         }
 
         if (fieldClass.isArray()) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
index c541fcbff..97b41f3e9 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterImplMap.java
@@ -1,6 +1,7 @@
 package com.alibaba.fastjson2.writer;
 
 import com.alibaba.fastjson2.*;
+import com.alibaba.fastjson2.codec.FieldInfo;
 import com.alibaba.fastjson2.filter.*;
 import com.alibaba.fastjson2.util.*;
 
@@ -54,6 +55,8 @@ public final class ObjectWriterImplMap
     final char[] typeInfoUTF16;
     final byte[] typeInfoUTF8;
 
+    final boolean contentAs;
+
     public ObjectWriterImplMap(Class objectClass, long features) {
         this(null, null, objectClass, objectClass, features);
     }
@@ -75,6 +78,7 @@ public final class ObjectWriterImplMap
         } else {
             this.valueTypeRefDetect = !ObjectWriterProvider.isNotReferenceDetect(TypeUtils.getClass(valueType));
         }
+        contentAs = (features & FieldInfo.CONTENT_AS) != 0;
 
         String typeName = TypeUtils.getTypeName(objectClass);
         String typeInfoStr = "\"@type\":\"" + objectClass.getName() + "\"";
@@ -288,7 +292,12 @@ public final class ObjectWriterImplMap
                 }
             }
 
-            Class<?> valueClass = value.getClass();
+            Class valueClass;
+            if (contentAs) {
+                valueClass = (Class) this.valueType;
+            } else {
+                valueClass = value.getClass();
+            }
             if (valueClass == String.class) {
                 jsonWriter.writeString((String) value);
                 continue;
@@ -491,7 +500,12 @@ public final class ObjectWriterImplMap
             }
             jsonWriter.writeColon();
 
-            Class<?> valueClass = value.getClass();
+            Class valueClass;
+            if (contentAs) {
+                valueClass = (Class) this.valueType;
+            } else {
+                valueClass = value.getClass();
+            }
             if (valueClass == String.class) {
                 jsonWriter.writeString((String) value);
                 continue;
@@ -667,8 +681,13 @@ public final class ObjectWriterImplMap
                 if (value == null) {
                     jsonWriter.writeNull();
                 } else {
-                    Class<?> valueType = value.getClass();
-                    ObjectWriter valueWriter = jsonWriter.getObjectWriter(valueType);
+                    Class valueClass;
+                    if (contentAs) {
+                        valueClass = (Class) this.valueType;
+                    } else {
+                        valueClass = value.getClass();
+                    }
+                    ObjectWriter valueWriter = jsonWriter.getObjectWriter(valueClass);
                     valueWriter.write(jsonWriter, value, fieldName, fieldType, this.features);
                 }
             } finally {


Test patch:
diff --git a/core/src/test/java/com/alibaba/fastjson2/aliyun/MapGhostTest.java b/core/src/test/java/com/alibaba/fastjson2/aliyun/MapGhostTest.java
index 7b437e284..7d89788bb 100644
--- a/core/src/test/java/com/alibaba/fastjson2/aliyun/MapGhostTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/aliyun/MapGhostTest.java
@@ -21,6 +21,8 @@ public class MapGhostTest {
         byte[] bytes1 = JSONB.toBytes(map, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.FieldBased);
         assertArrayEquals(bytes, bytes1);
 
+        System.out.println(JSONB.toJSONString(bytes, true));
+
         MapGhost mapGhost = (MapGhost) JSONB.parseObject(
                 bytes, Object.class,
                 JSONReader.Feature.FieldBased,
diff --git a/core/src/test/java/com/alibaba/fastjson2/features/ContentAsTest.java b/core/src/test/java/com/alibaba/fastjson2/features/ContentAsTest.java
new file mode 100644
index 000000000..2ec4fc3ce
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/features/ContentAsTest.java
@@ -0,0 +1,111 @@
+package com.alibaba.fastjson2.features;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ContentAsTest {
+    class Vehicle {
+        private String type;
+
+        public Vehicle(String type) { this.type = type; }
+        public String getType() { return type; }
+    }
+
+    class Car
+            extends Vehicle {
+        private int seats;
+
+        public Car(String type, int seats) {
+            super(type);
+            this.seats = seats;
+        }
+        public int getSeats() { return seats; }
+    }
+
+    class Garage {
+        @JSONField(contentAs = Vehicle.class)
+        private List<Vehicle> vehicles = new ArrayList<>();
+
+        public void addVehicle(Vehicle v) { vehicles.add(v); }
+        public List<Vehicle> getVehicles() { return vehicles; }
+    }
+
+    @Test
+    public void test() throws Exception {
+        Garage garage = new Garage();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        assertEquals(
+                "{\"vehicles\":[{\"type\":\"Sedan\"},{\"type\":\"SUV\"}]}",
+                JSON.toJSONString(garage));
+    }
+
+    class GarageField {
+        @JSONField(contentAs = Vehicle.class)
+        public List<Vehicle> vehicles = new ArrayList<>();
+
+        public void addVehicle(Vehicle v) { vehicles.add(v); }
+    }
+
+    @Test
+    public void testField() throws Exception {
+        GarageField garage = new GarageField();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        assertEquals(
+                "{\"vehicles\":[{\"type\":\"Sedan\"},{\"type\":\"SUV\"}]}",
+                JSON.toJSONString(garage));
+    }
+
+    class GarageMap {
+        @JSONField(contentAs = Vehicle.class)
+        private Map<String, Vehicle> vehicles = new LinkedHashMap<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.put(v.getType(), v);
+        }
+
+        public Map<String, Vehicle> getVehicles() {
+            return vehicles;
+        }
+    }
+
+    @Test
+    public void testMap() throws Exception {
+        GarageMap garage = new GarageMap();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        String json = JSON.toJSONString(garage);
+        assertEquals("{\"vehicles\":{\"Sedan\":{\"type\":\"Sedan\"},\"SUV\":{\"type\":\"SUV\"}}}", json);
+    }
+
+    class GarageMapField {
+        @JSONField(contentAs = Vehicle.class)
+        public Map<String, Vehicle> vehicles = new LinkedHashMap<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.put(v.getType(), v);
+        }
+    }
+
+    @Test
+    public void testMapField() throws Exception {
+        GarageMapField garage = new GarageMapField();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        String json = JSON.toJSONString(garage);
+        assertEquals("{\"vehicles\":{\"Sedan\":{\"type\":\"Sedan\"},\"SUV\":{\"type\":\"SUV\"}}}", json);
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonSerializeTest.java b/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonSerializeTest.java
new file mode 100644
index 000000000..400e3ca60
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/jackson_support/JsonSerializeTest.java
@@ -0,0 +1,133 @@
+package com.alibaba.fastjson2.jackson_support;
+
+import com.alibaba.fastjson2.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JsonSerializeTest {
+    class Vehicle {
+        private String type;
+
+        public Vehicle(String type) {
+            this.type = type;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+
+    class Car
+            extends Vehicle {
+        private int seats;
+
+        public Car(String type, int seats) {
+            super(type);
+            this.seats = seats;
+        }
+
+        public int getSeats() {
+            return seats;
+        }
+    }
+
+    class Garage {
+        @JsonSerialize(contentAs = Vehicle.class)
+        private List<Vehicle> vehicles = new ArrayList<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.add(v);
+        }
+
+        public List<Vehicle> getVehicles() {
+            return vehicles;
+        }
+    }
+
+    @Test
+    public void test() throws Exception {
+        Garage garage = new Garage();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        ObjectMapper mapper = new ObjectMapper();
+        String jackson = mapper.writeValueAsString(garage);
+        assertEquals("{\"vehicles\":[{\"type\":\"Sedan\"},{\"type\":\"SUV\"}]}", jackson);
+        assertEquals(jackson, JSON.toJSONString(garage));
+    }
+
+    class GarageMap {
+        @JsonSerialize(contentAs = Vehicle.class)
+        private Map<String, Vehicle> vehicles = new LinkedHashMap<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.put(v.getType(), v);
+        }
+
+        public Map<String, Vehicle> getVehicles() {
+            return vehicles;
+        }
+    }
+
+    @Test
+    public void testMap() throws Exception {
+        GarageMap garage = new GarageMap();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        ObjectMapper mapper = new ObjectMapper();
+        String jackson = mapper.writeValueAsString(garage);
+        assertEquals("{\"vehicles\":{\"Sedan\":{\"type\":\"Sedan\"},\"SUV\":{\"type\":\"SUV\"}}}", jackson);
+        assertEquals(jackson, JSON.toJSONString(garage));
+    }
+
+    class GarageField {
+        @JsonSerialize(contentAs = Vehicle.class)
+        public List<Vehicle> vehicles = new ArrayList<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.add(v);
+        }
+    }
+
+    @Test
+    public void testField() throws Exception {
+        GarageField garage = new GarageField();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        ObjectMapper mapper = new ObjectMapper();
+        String jackson = mapper.writeValueAsString(garage);
+        assertEquals("{\"vehicles\":[{\"type\":\"Sedan\"},{\"type\":\"SUV\"}]}", jackson);
+        assertEquals(jackson, JSON.toJSONString(garage));
+    }
+
+    class GarageMapField {
+        @JsonSerialize(contentAs = Vehicle.class)
+        public Map<String, Vehicle> vehicles = new LinkedHashMap<>();
+
+        public void addVehicle(Vehicle v) {
+            vehicles.put(v.getType(), v);
+        }
+    }
+
+    @Test
+    public void testMapField() throws Exception {
+        GarageMapField garage = new GarageMapField();
+        garage.addVehicle(new Car("Sedan", 5));
+        garage.addVehicle(new Car("SUV", 7));
+
+        ObjectMapper mapper = new ObjectMapper();
+        String jackson = mapper.writeValueAsString(garage);
+        assertEquals("{\"vehicles\":{\"Sedan\":{\"type\":\"Sedan\"},\"SUV\":{\"type\":\"SUV\"}}}", jackson);
+        assertEquals(jackson, JSON.toJSONString(garage));
+    }
+}