Repository: alibaba/fastjson2
Pull request #3560 (2025-05-16 18:56:24)

fix only reuse initObjectWriter initialized by JSONField and add miss…
…ing colon, for issue #3546 and issue #2286

### What this PR does / why we need it?

fix only reuse initObjectWriter initialized by JSONField and add missing colon

### 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/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
index 36af401c3..f4316bf59 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
@@ -855,6 +855,7 @@ public abstract class JSONWriter
 
     public final void writeNameValue(String name, Object value) {
         writeName(name);
+        writeColon();
         writeAny(value);
     }
 
diff --git a/core/src/main/java/com/alibaba/fastjson2/SymbolTable.java b/core/src/main/java/com/alibaba/fastjson2/SymbolTable.java
index b83f2f957..afc13a717 100644
--- a/core/src/main/java/com/alibaba/fastjson2/SymbolTable.java
+++ b/core/src/main/java/com/alibaba/fastjson2/SymbolTable.java
@@ -15,6 +15,24 @@ public final class SymbolTable {
     private final long[] hashCodes;
     private final long[] hashCodesOrigin;
 
+    /**
+     * Create a symbol table
+     *
+     * @param input input
+     * @since 2.0.58
+     */
+    public SymbolTable(Class<?>... input) {
+        this(classNames(input));
+    }
+
+    private static String[] classNames(Class... input) {
+        String[] names = new String[input.length];
+        for (int i = 0; i < input.length; i++) {
+            names[i] = input[i].getName();
+        }
+        return names;
+    }
+
     public SymbolTable(String... input) {
         Set<String> set = new TreeSet<>(Arrays.asList(input));
         names = new String[set.size()];
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 55a47a052..f139c7e7a 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java
@@ -282,11 +282,6 @@ public class ObjectReaderBaseModule
                             processJacksonJsonFormat(beanInfo, annotation);
                         }
                         break;
-                    case "com.fasterxml.jackson.annotation.JsonInclude":
-                        if (useJacksonAnnotation) {
-                            processJacksonJsonInclude(beanInfo, annotation);
-                        }
-                        break;
                     case "com.fasterxml.jackson.annotation.JsonSubTypes":
                         if (useJacksonAnnotation) {
                             processJacksonJsonSubTypes(beanInfo, annotation);
@@ -859,11 +854,6 @@ public class ObjectReaderBaseModule
                             processGsonSerializedName(fieldInfo, annotation);
                         }
                         break;
-                    case "com.fasterxml.jackson.annotation.JsonInclude":
-                        if (useJacksonAnnotation) {
-                            processJacksonJsonInclude(fieldInfo, annotation);
-                        }
-                        break;
                     default:
                         break;
                 }
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java
index 40e6be854..9ef870cdc 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java
@@ -65,6 +65,7 @@ public abstract class BeanUtils {
             3724195282986200606L,
             3742915795806478647L,
             3977020351318456359L,
+            4775491097662790952L,
             4882459834864833642L,
             6033839080488254886L,
             7981148566008458638L,
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 9817721d0..7522f17c3 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/FieldWriterObject.java
@@ -25,6 +25,7 @@ public class FieldWriterObject<T>
     final boolean unwrapped;
     final boolean array;
     final boolean number;
+    protected boolean writeUsing;
 
     static final AtomicReferenceFieldUpdater<FieldWriterObject, Class> initValueClassUpdater = AtomicReferenceFieldUpdater.newUpdater(
             FieldWriterObject.class,
@@ -76,7 +77,7 @@ public class FieldWriterObject<T>
             return getObjectWriterVoid(jsonWriter, valueClass);
         } else {
             boolean typeMatch = initValueClass == valueClass
-                    || (initValueClass.isAssignableFrom(valueClass) && !jsonWriter.isEnabled(WriteClassName) && fieldType instanceof Class)
+                    || (writeUsing && initValueClass.isAssignableFrom(valueClass))
                     || (initValueClass == Map.class && initValueClass.isAssignableFrom(valueClass))
                     || (initValueClass == List.class && initValueClass.isAssignableFrom(valueClass));
             if (!typeMatch && initValueClass.isPrimitive()) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterAdapter.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterAdapter.java
index 4269769b0..f6f9cc94f 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterAdapter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterAdapter.java
@@ -687,7 +687,11 @@ public class ObjectWriterAdapter<T>
                 }
                 if (valueWriter instanceof ObjectWriterAdapter) {
                     ObjectWriterAdapter objectWriterAdapter = (ObjectWriterAdapter) valueWriter;
-                    fieldValue = objectWriterAdapter.toJSONObject(fieldValue);
+                    if (!objectWriterAdapter.getFieldWriters().isEmpty()) {
+                        fieldValue = objectWriterAdapter.toJSONObject(fieldValue);
+                    } else {
+                        fieldValue = JSON.toJSON(fieldValue);
+                    }
                 }
             }
             jsonObject.put(fieldWriter.fieldName, fieldValue);
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 1eff38c39..23e151e34 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreator.java
@@ -353,6 +353,9 @@ public class ObjectWriterCreator {
                 fieldInfo.init();
                 FieldWriter fieldWriter = creteFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field);
                 if (fieldWriter != null) {
+                    if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                        ((FieldWriterObject) fieldWriter).writeUsing = true;
+                    }
                     fieldWriterMap.put(fieldWriter.fieldName, fieldWriter);
                 }
             });
@@ -376,6 +379,9 @@ public class ObjectWriterCreator {
                         fieldInfo.ignore = (field.getModifiers() & Modifier.PUBLIC) == 0;
                         FieldWriter fieldWriter = creteFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field);
                         if (fieldWriter != null) {
+                            if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                                ((FieldWriterObject) fieldWriter).writeUsing = true;
+                            }
                             FieldWriter origin = fieldWriterMap.putIfAbsent(fieldWriter.fieldName, fieldWriter);
 
                             if (origin != null && origin.compareTo(fieldWriter) > 0) {
@@ -490,6 +496,9 @@ public class ObjectWriterCreator {
                         );
                     }
 
+                    if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                        ((FieldWriterObject) fieldWriter).writeUsing = true;
+                    }
                     FieldWriter origin = fieldWriterMap.putIfAbsent(fieldWriter.fieldName, fieldWriter);
 
                     if (origin != null && origin.compareTo(fieldWriter) > 0) {
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 5c0dd1074..308b5aba5 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java
@@ -226,6 +226,9 @@ public class ObjectWriterCreatorASM
 
                         FieldWriter fieldWriter = creteFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field);
                         if (fieldWriter != null) {
+                            if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                                ((FieldWriterObject) fieldWriter).writeUsing = true;
+                            }
                             FieldWriter origin = fieldWriterMap.putIfAbsent(fieldWriter.fieldName, fieldWriter);
                             if (origin != null) {
                                 int cmp = origin.compareTo(fieldWriter);
@@ -347,6 +350,9 @@ public class ObjectWriterCreatorASM
                         );
                     }
 
+                    if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                        ((FieldWriterObject) fieldWriter).writeUsing = true;
+                    }
                     FieldWriter origin = fieldWriterMap.putIfAbsent(fieldName, fieldWriter);
 
                     if (origin != null && origin.compareTo(fieldWriter) > 0) {
@@ -374,6 +380,9 @@ public class ObjectWriterCreatorASM
                 fieldInfo.init();
                 FieldWriter fieldWriter = creteFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field);
                 if (fieldWriter != null) {
+                    if (fieldInfo.writeUsing != null && fieldWriter instanceof FieldWriterObject) {
+                        ((FieldWriterObject) fieldWriter).writeUsing = true;
+                    }
                     fieldWriterMap.put(fieldWriter.fieldName, fieldWriter);
                 }
             });
diff --git a/docs/fastjson_1_upgrade_cn.md b/docs/fastjson_1_upgrade_cn.md
index 052733a42..66eebb6e5 100644
--- a/docs/fastjson_1_upgrade_cn.md
+++ b/docs/fastjson_1_upgrade_cn.md
@@ -103,8 +103,8 @@ FASTJSON v2中有比较完善的扩展机制，如下：
 | com.alibaba.fastjson.serializer.SerializeConfig             | com.alibaba.fastjson2.writer.ObjectWriterProvider     |
 | com.alibaba.fastjson.serializer.ToStringSerializer          | com.alibaba.fastjson2.writer.ObjectWriterImplToString |
 | com.alibaba.fastjson.serializer.ValueFilter                 | com.alibaba.fastjson2.filter.ValueFilter              |
-| com.alibaba.fastjson.serializer.SerializerFeature           | com.alibaba.fastjson2.JSONReader.Feature              |
-| com.alibaba.fastjson.parser.Feature                         | com.alibaba.fastjson2.JSONWriter.Feature              |
+| com.alibaba.fastjson.serializer.SerializerFeature           | com.alibaba.fastjson2.JSONWriter.Feature              |
+| com.alibaba.fastjson.parser.Feature                         | com.alibaba.fastjson2.JSONReader.Feature              |
 
 
 ### 3.4 SerializerFeature.UseISO8601DateFormat在fastjson2的替代方案
diff --git a/extension/pom.xml b/extension/pom.xml
index 2e8547786..852037705 100644
--- a/extension/pom.xml
+++ b/extension/pom.xml
@@ -152,7 +152,7 @@
         <dependency>
             <groupId>com.clickhouse</groupId>
             <artifactId>clickhouse-jdbc</artifactId>
-            <version>0.8.3</version>
+            <version>0.8.6</version>
             <classifier>http</classifier>
             <scope>test</scope>
         </dependency>
diff --git a/pom.xml b/pom.xml
index 7e153a469..a7cef5923 100644
--- a/pom.xml
+++ b/pom.xml
@@ -280,7 +280,7 @@
             <dependency>
                 <groupId>io.projectreactor</groupId>
                 <artifactId>reactor-core</artifactId>
-                <version>3.7.4</version>
+                <version>3.7.7</version>
             </dependency>
             <dependency>
                 <groupId>io.springfox</groupId>


Test patch:
diff --git a/core/src/test/java/com/alibaba/fastjson2/SymbolTableTest.java b/core/src/test/java/com/alibaba/fastjson2/SymbolTableTest.java
new file mode 100644
index 000000000..bbc46aead
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/SymbolTableTest.java
@@ -0,0 +1,14 @@
+package com.alibaba.fastjson2;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SymbolTableTest {
+    @Test
+    public void test() {
+        SymbolTable symbolTable = new SymbolTable(Integer.class, Long.class);
+        assertEquals(Integer.class.getName(), symbolTable.getName(1));
+        assertEquals(Long.class.getName(), symbolTable.getName(2));
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java b/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java
index da46d322e..13e9d773b 100644
--- a/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/features/PrettyFormatTest.java
@@ -242,14 +242,14 @@ public class PrettyFormatTest {
         jsonWriter.close();
 
         assertEquals("{\n" +
-                "\t\"id\"123\n" +
+                "\t\"id\":123\n" +
                 "}", jsonWriter.toString());
 
         jsonWriter.incrementIndent();
         assertEquals(1, jsonWriter.level());
         jsonWriter.println();
         assertEquals("{\n" +
-                "\t\"id\"123\n" +
+                "\t\"id\":123\n" +
                 "}\n\t", jsonWriter.toString());
         jsonWriter.decrementIdent();
         assertEquals(0, jsonWriter.level());
@@ -266,14 +266,14 @@ public class PrettyFormatTest {
         jsonWriter.close();
 
         assertEquals("{\n" +
-                "\t\"id\"123\n" +
+                "\t\"id\":123\n" +
                 "}", jsonWriter.toString());
 
         jsonWriter.incrementIndent();
         assertEquals(1, jsonWriter.level());
         jsonWriter.println();
         assertEquals("{\n" +
-                "\t\"id\"123\n" +
+                "\t\"id\":123\n" +
                 "}\n\t", jsonWriter.toString());
         jsonWriter.decrementIdent();
         assertEquals(0, jsonWriter.level());
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_2200/Issue2286.java b/core/src/test/java/com/alibaba/fastjson2/issues_2200/Issue2286.java
index b4855c45e..fd671f89b 100644
--- a/core/src/test/java/com/alibaba/fastjson2/issues_2200/Issue2286.java
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_2200/Issue2286.java
@@ -8,14 +8,14 @@ import org.junit.jupiter.api.Test;
 
 import java.lang.reflect.Type;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 public class Issue2286 {
     @Test
     public void test() {
         Bean bean = new Bean();
         bean.animal = new Cat();
-
-        String str = JSON.toJSONString(bean);
-        System.out.println(str);
+        assertEquals("{\"animal\":{\"type\":\"Cat\"}}", JSON.toJSONString(bean));
     }
 
     public static class Bean {
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3546.java b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3546.java
new file mode 100644
index 000000000..a657d9a1d
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3546.java
@@ -0,0 +1,47 @@
+package com.alibaba.fastjson2.issues_3500;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3546 {
+    @Test
+    public void test() {
+        TextMessage textMsg = new TextMessage();
+
+        // 第一次设置父类实例
+        textMsg.setData(new Message());
+        assertEquals("{\"data\":{\"channel\":null}}", JSON.toJSONString(textMsg, JSONWriter.Feature.WriteNulls));
+
+        // 第二次设置子类实例
+        AssignMessage am = new AssignMessage();
+        am.setAssignee("assignee");
+        textMsg.setData(am);
+
+        assertEquals("{\"data\":{\"assignee\":\"assignee\",\"channel\":null}}", JSON.toJSONString(textMsg, JSONWriter.Feature.WriteNulls));
+    }
+
+    @Getter
+    @Setter
+    class Message {
+        private String channel;
+        // getter/setter...
+    }
+
+    @Getter
+    @Setter
+    class AssignMessage
+            extends Message {
+        private String assignee;
+    }
+
+    @Getter
+    @Setter
+    class TextMessage {
+        private Object data;
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3559.java b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3559.java
new file mode 100644
index 000000000..43c158f69
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3559.java
@@ -0,0 +1,21 @@
+package com.alibaba.fastjson2.issues_3500;
+
+import com.alibaba.fastjson2.JSON;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.io.FileSystemResource;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3559 {
+    @Test
+    public void test() throws IOException {
+        FileSystemResource resource = new FileSystemResource(this.getClass().getClassLoader().getResource("issues/issue3559.json").getPath());
+        long length = resource.contentLength();
+        long modified = resource.lastModified();
+        JSON.toJSONString(resource);
+        assertEquals(length, resource.contentLength());
+        assertEquals(modified, resource.lastModified());
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3569.java b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3569.java
new file mode 100644
index 000000000..9bee112d5
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3569.java
@@ -0,0 +1,33 @@
+package com.alibaba.fastjson2.issues_3500;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3569 {
+    @Test
+    public void test() {
+        TestClass<String> testClass1 = new TestClass<>("hello");
+        JSONObject jsonObject = JSONObject.from(testClass1);
+        assertEquals(jsonObject.toString(), "{\"generic\":\"hello\"}");
+
+        TestClass<TestData> testClass2 = new TestClass<>(new TestData("hello"));
+        jsonObject = JSONObject.from(testClass2);
+        assertEquals(jsonObject.toString(), "{\"generic\":{\"testData\":\"hello\"}}");
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class TestClass<T> {
+        private T generic;
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class TestData {
+        private String testData;
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3577.java b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3577.java
new file mode 100644
index 000000000..2264dc737
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3500/Issue3577.java
@@ -0,0 +1,92 @@
+package com.alibaba.fastjson2.issues_3500;
+
+import com.alibaba.fastjson2.JSON;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import org.junit.jupiter.api.Test;
+
+import java.io.Serializable;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class Issue3577 {
+    @Test
+    public void test() {
+        String json = "{            \n" +
+                "            \"certifiedMaterials\": {\n" +
+                "              \"show\": false,\n" +
+                "              \"materials\": []\n" +
+                "            }\n" +
+                "          }";
+
+        ColorBrandsProduct dto = JSON.parseObject(json, ColorBrandsProduct.class);
+        assertNotNull(dto);
+        assertNull(dto.getCertifiedMaterials().getContainsAtLeastTitle());
+    }
+
+    @JsonPropertyOrder(ColorBrandsProduct.JSON_PROPERTY_CERTIFIED_MATERIALS)
+    @JsonTypeName("Color")
+    public static class ColorBrandsProduct
+            implements Serializable {
+        private static final long serialVersionUID = 1L;
+        public static final String JSON_PROPERTY_CERTIFIED_MATERIALS = "certifiedMaterials";
+        private CertifiedMaterialsBrandsProduct certifiedMaterials;
+        public ColorBrandsProduct certifiedMaterials(CertifiedMaterialsBrandsProduct certifiedMaterials) {
+            this.certifiedMaterials = certifiedMaterials;
+            return this;
+        }
+
+        /**
+         * Get certifiedMaterials
+         *
+         * @return certifiedMaterials
+         **/
+        @JsonProperty(JSON_PROPERTY_CERTIFIED_MATERIALS)
+        @JsonInclude(value = JsonInclude.Include.ALWAYS)
+
+        public CertifiedMaterialsBrandsProduct getCertifiedMaterials() {
+            return certifiedMaterials;
+        }
+
+        @JsonProperty(JSON_PROPERTY_CERTIFIED_MATERIALS)
+        @JsonInclude(value = JsonInclude.Include.ALWAYS)
+        public void setCertifiedMaterials(CertifiedMaterialsBrandsProduct certifiedMaterials) {
+            this.certifiedMaterials = certifiedMaterials;
+        }
+    }
+
+    @JsonPropertyOrder(CertifiedMaterialsBrandsProduct.JSON_PROPERTY_CONTAINS_AT_LEAST_TITLE)
+    @JsonTypeName("CertifiedMaterials")
+    public static class CertifiedMaterialsBrandsProduct
+            implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        public static final String JSON_PROPERTY_CONTAINS_AT_LEAST_TITLE = "containsAtLeastTitle";
+        private String containsAtLeastTitle;
+
+        public CertifiedMaterialsBrandsProduct containsAtLeastTitle(String containsAtLeastTitle) {
+            this.containsAtLeastTitle = containsAtLeastTitle;
+            return this;
+        }
+
+        /**
+         * Title that says \&quot;contains at least\&quot;, translated to the given store language.
+         *
+         * @return containsAtLeastTitle
+         **/
+        @JsonProperty(JSON_PROPERTY_CONTAINS_AT_LEAST_TITLE)
+        // @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
+
+        public String getContainsAtLeastTitle() {
+            return containsAtLeastTitle;
+        }
+
+        @JsonProperty(JSON_PROPERTY_CONTAINS_AT_LEAST_TITLE)
+        @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
+        public void setContainsAtLeastTitle(String containsAtLeastTitle) {
+            this.containsAtLeastTitle = containsAtLeastTitle;
+        }
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/util/BeanUtilsTest.java b/core/src/test/java/com/alibaba/fastjson2/util/BeanUtilsTest.java
index 19b7c7324..e5d602cde 100644
--- a/core/src/test/java/com/alibaba/fastjson2/util/BeanUtilsTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/util/BeanUtilsTest.java
@@ -307,7 +307,8 @@ public class BeanUtilsTest {
                 "java.util.concurrent.locks.Lock",
                 "java.util.concurrent.locks.ReentrantLock",
                 "java.security.ProtectionDomain",
-                "java.lang.ref.ReferenceQueue"
+                "java.lang.ref.ReferenceQueue",
+                "java.io.OutputStream"
         };
 
         long[] hashCodes = new long[names.length];
diff --git a/core/src/test/resources/issues/issue3559.json b/core/src/test/resources/issues/issue3559.json
new file mode 100644
index 000000000..2c63c0851
--- /dev/null
+++ b/core/src/test/resources/issues/issue3559.json
@@ -0,0 +1,2 @@
+{
+}