Repository: alibaba/fastjson2
Pull request #3492 (2025-04-13 00:26:49)

fix NPE when enable unnecessary ReferenceDetection, for issue #3432
### What this PR does / why we need it?

fix NPE when enable unnecessary ReferenceDetection

### 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/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index f198350a1..44f8f4bc3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -18,7 +18,7 @@ jobs:
     timeout-minutes: 20
     strategy:
       matrix:
-        os: [ ubuntu-20.04, windows-latest, macos-latest ]
+        os: [ ubuntu-22.04, windows-latest, macos-latest ]
         java: [ 8, 11, 17, 21 ]
       fail-fast: false
       max-parallel: 16
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
index 2cb955958..942a846e6 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
@@ -224,6 +224,9 @@ public abstract class JSONWriter
     }
 
     public final String setPath0(int index, Object object) {
+        if (path == null) {
+            return null;
+        }
         this.path = index == 0
                 ? (path.child0 != null ? path.child0 : (path.child0 = new Path(path, index)))
                 : index == 1
diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderObject.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderObject.java
index ba79f51a6..19fad388d 100644
--- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderObject.java
+++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderObject.java
@@ -182,7 +182,9 @@ public class FieldReaderObject<T>
                 jsonReader.skipValue();
                 return;
             } else if ((contextFeatures & JSONReader.Feature.ErrorOnNoneSerializable.mask) != 0) {
-                throw new JSONException("not support none-Serializable");
+                if (fieldClass != Object.class || (jsonReader.isObject() || jsonReader.isArray())) {
+                    throw new JSONException("not support none-Serializable");
+                }
             }
         }
 
diff --git a/docs/benchmark/benchmark_2.0.12.md b/docs/benchmark/benchmark_2.0.12.md
index 06517c066..62436148a 100644
--- a/docs/benchmark/benchmark_2.0.12.md
+++ b/docs/benchmark/benchmark_2.0.12.md
@@ -50,7 +50,7 @@ Bean bean = JSON.parseJSONObject(str, Bean.class);
 
 这个场景是fastjson1最强的场景；
 
-在ecs.c7.xlarge-oracle-jdk1.8.0_341_x64环境中，只相当于只相当于fastjson2的76.16%;jackson则只相当于fastjson2的41.62%; gson是fastjson2的34.44%.
+在ecs.c7.xlarge-oracle-jdk1.8.0_341_x64环境中，只相当于fastjson2的76.16%；jackson则只相当于fastjson2的41.62%；gson是fastjson2的34.44%。
 
 性能排序如下：
 ```java
@@ -224,7 +224,7 @@ byte[] utf8Bytes = ...;
 Bean bean = JSON.parseJSONObject(utf8Bytes, Bean.class);
 ```
 
-这个场景fastjson2同样表现出了卓越的性能；在JDK8下，fastjson1和jackson的性能分别之后fastjson2的50.49%和63.22%;
+这个场景fastjson2同样表现出了卓越的性能；在JDK8下，fastjson1和jackson的性能分别之后fastjson2的50.49%和63.22%；
 gson不直接支持输入utf8Bytes，需要先构造字符串，性能只有fastjson2的35%。
 
 性能排序如下：
@@ -312,7 +312,7 @@ byte[] utf8Bytes = ...;
 JSONObject jsonObject = JSON.parseJSONObject(utf8Bytes);
 ```
 
-这个场景fastjson2同样表现出了卓越的性能；在JDK8下，fastjson1和jackson的性能分别之后fastjson2的50.49%和63.22%;
+这个场景fastjson2同样表现出了卓越的性能；在JDK8下，fastjson1和jackson的性能分别之后fastjson2的50.49%和63.22%；
 gson不直接支持输入utf8Bytes，需要先构造字符串，性能只有fastjson2的35%。
 
 性能排序如下：


Test patch:
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3432.java b/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3432.java
new file mode 100644
index 000000000..e278b3c92
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3432.java
@@ -0,0 +1,31 @@
+package com.alibaba.fastjson2.issues_3400;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class Issue3432 {
+    @Test
+    public void test_toJSON() {
+        List<Node> nodes = new ArrayList<>();
+        Node node = new Node();
+        node.setGradeId("123");
+        node.setPrivilegeIds(Arrays.asList("1", "2"));
+        nodes.add(node);
+
+        assertEquals(com.alibaba.fastjson.JSON.toJSON(nodes), JSON.toJSON(nodes, JSONWriter.Feature.ReferenceDetection));
+    }
+
+    @Data
+    public static class Node {
+        private String gradeId;
+        private List<String> privilegeIds;
+    }
+}
diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3499.java b/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3499.java
new file mode 100644
index 000000000..6c54af25e
--- /dev/null
+++ b/core/src/test/java/com/alibaba/fastjson2/issues_3400/Issue3499.java
@@ -0,0 +1,44 @@
+package com.alibaba.fastjson2.issues_3400;
+
+import com.alibaba.fastjson2.JSONB;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.ContextAutoTypeBeforeHandler;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.junit.jupiter.api.Test;
+
+import java.io.Serializable;
+
+public class Issue3499 {
+    @Test
+    public void test() {
+        Message<String> obj = new Message<>("Hello");
+        byte[] bytes = JSONB.toBytes(obj, JSONWriter.Feature.WriteClassName,
+                JSONWriter.Feature.FieldBased,
+                JSONWriter.Feature.ErrorOnNoneSerializable,
+                JSONWriter.Feature.ReferenceDetection,
+                JSONWriter.Feature.WriteNulls,
+                JSONWriter.Feature.NotWriteDefaultValue,
+                JSONWriter.Feature.NotWriteHashMapArrayListClassName,
+                JSONWriter.Feature.WriteNameAsSymbol);
+        ContextAutoTypeBeforeHandler handler = new ContextAutoTypeBeforeHandler(true, obj.getClass());
+        Object result = JSONB.parseObject(bytes, obj.getClass(), handler,
+                JSONReader.Feature.UseDefaultConstructorAsPossible,
+                JSONReader.Feature.ErrorOnNoneSerializable,
+                JSONReader.Feature.IgnoreAutoTypeNotMatch,
+                JSONReader.Feature.UseNativeObject,
+                JSONReader.Feature.FieldBased);
+    }
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    @AllArgsConstructor
+    static class Message<T>
+            implements Serializable {
+        T msg;
+    }
+}