alibaba / fastjson2

🚄 FASTJSON2 is a Java JSON library with excellent performance.
Apache License 2.0
3.79k stars 495 forks source link

[BUG] 在使用了JSONWriter.Feature.WriteClassName后,FastJson2无法正确处理Map<String, Integer> #3153

Open fwhdzh opened 1 week ago

fwhdzh commented 1 week ago

问题描述

我有一个类

    public static class TestState {

        Map<String, Integer> currentTerm;

        public TestState(Map<String, Integer> currentTerm) {
            this.currentTerm = currentTerm;
        }
    }

如果我在序列化时使用了JSONWriter.Feature.WriteClassName,那么在反序列化处理时,FastJson2在处理currentTerm时会报错com.alibaba.fastjson2.JSONException: parseInt error, value : java.util.HashMap

环境信息

重现步骤

可使用下列完整单元测试代码来复现该缺陷

import java.util.Map;

import org.junit.jupiter.api.Test;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;

public class TestFastJSON {

    public static class TestState {

        Map<String, Integer> currentTerm;

        public TestState(Map<String, Integer> currentTerm) {
            this.currentTerm = currentTerm;
        }
    }

    private static final Filter autoTypeFilter = JSONReader.autoTypeFilter(
            "com.",
            "org.",
            "java.",
            "edu.");

    public JSONWriter.Feature[] features = new JSONWriter.Feature[] {
            JSONWriter.Feature.WriteClassName,
            JSONWriter.Feature.FieldBased,
    };

    public JSONReader.Feature[] readerFeatures = new JSONReader.Feature[] {
            JSONReader.Feature.FieldBased,
    };

    @Test
    void testFastJSON() {
        Map<String, Integer> currentTerm = new java.util.HashMap<>();
        currentTerm.put("N1", 2);
        currentTerm.put("N2", 2);
        currentTerm.put("N3", 1);
        TestState ts = new TestState(currentTerm);
        String stateJSONStr = JSONObject.toJSONString(ts, features);
        TestState deseriObj = JSON.parseObject(stateJSONStr, TestState.class,
                autoTypeFilter,
                readerFeatures);
    }
}

将上述代码复制到任意支持Junit 5的测试框架中,运行testFastJSON单元测试,可观察到完整报错信息如下:

com.alibaba.fastjson2.JSONException: parseInt error, value : java.util.HashMap
 at com.alibaba.fastjson2.JSONReader.toInt32(JSONReader.java:3050)
 at com.alibaba.fastjson2.JSONReader.getInt32Value(JSONReader.java:843)
 at com.alibaba.fastjson2.JSONReaderUTF16.readInt32(JSONReaderUTF16.java:2506)
 at com.alibaba.fastjson2.reader.ObjectReaderImplInteger.readObject(ObjectReaderImplInteger.java:22)
 at com.alibaba.fastjson2.reader.ObjectReaderImplMapTyped.readObject(ObjectReaderImplMapTyped.java:408)
 at com.alibaba.fastjson2.reader.ORG_1_1_TestState.readObject(Unknown Source)
 at com.alibaba.fastjson2.reader.ObjectReaderAdapter.autoType(ObjectReaderAdapter.java:247)
 at com.alibaba.fastjson2.reader.ORG_1_1_TestState.readObject(Unknown Source)
 at com.alibaba.fastjson2.JSON.parseObject(JSON.java:906)
 at ...TestFastJSON.testFastJSON(TestFastJSON.java:48)
 at java.util.ArrayList.forEach(ArrayList.java:1259)
 at java.util.ArrayList.forEach(ArrayList.java:1259)

期待的正确结果

可以正确的将TestState反序列化

相关日志输出

如上

附加信息

CodePlayer commented 4 days ago

这并非bug,而是用法问题。

因为你设置了 JSONWriter.Feature.WriteClassName 了,所以序列化 JSON 时,会多增加一个 "@type" 属性。 反序列化的时候,这个属性的值因为不是 Integer,所以就会报错。

此时,你可以在 readerFeatures 数组中加一个 JSONReader.Feature.SupportAutoType,就不会报错了。 因为 WriteClassNameSupportAutoType 两者一般都是互相配合起来使用,以实现 序列化/反序列化的。

加了 SupportAutoType 后,你可以在反序列化为Java对象时无需指定 类型参数(也就是你的 TestState.class),因为 它可以根据 JSON 字符串中的 @type 属性就能知道要序列化为哪个类型的对象。

例如:

public JSONReader.Feature[] readerFeatures = new JSONReader.Feature[] {
        JSONReader.Feature.FieldBased,
        JSONReader.Feature.SupportAutoType // 自动根据 @type 属性获取类型信息
};
// ...
TestState deseriObj = (TestState) JSON.parse(stateJSONStr, readerFeatures); // 无需指定类型
fwhdzh commented 3 days ago

目前最新版本的FaustJson2中,显示JSONReader.Feature.SupportAutoType已被弃用,具体信息为“It is not safe to explicitly turn on autoType, it is recommended to use AutoTypeBeforeHandler”。

我基于 #2178 编写了上面的测试用例,然而看起来并不能正常工作。

请问上面的测试用例中的JSONReader.autoTypeFilter使用是否有问题?如果确实存在问题,应该如何正确使用JSONReader.autoTypeFilter?

CodePlayer commented 3 days ago

你这个例子,我在本地测试过,是OK的,你所谓的不能正常工作的例子,有完整版的可以发出来吗?

另外,JSONReader.Feature.SupportAutoType 这个枚举之所以被标记为已弃用,是因为它存在安全隐患。 你可以想一想,如果外部随便传入一个 JSON字符串,里面也有 @type,而且它的值可能是一些导致安全隐患的类型名称,比如 java.lang.Systemjava.lang.Runtime 之类的,那么是不是可能因为设置了某些属性或方法,而产生安全隐患 ?

所以:

  1. 要么,只有自己可信可控的 JSON 字符串来源,才能使用这个枚举。比如 是你自己将可信的类型对象序列化后存入 Redis ,又是你自己从 Redis 中反序列化回来。当然,你也可以通过 AutoTypeBeforeHandler,设置一个包名的白名单,比如 限制只能将 com.your.package 包下面的类进行自动类型转换。
  2. 要么,就不要使用 WriteClassNameSupportAutoType 这一对组合枚举,自己每次在反序列化时手动指定类型,就像你写的 JSON.parseObject(stateJSONStr, TestState.class) 一样,这种情况是不需要设置 WriteClassName 来序列化的。
fwhdzh commented 3 days ago

完整的例子已经放到了https://github.com/fwhdzh/fastjsontest

所使用的环境为 OS信息: Ubuntu 22.04.3 LTS JDK信息: Oracle JDK 1.8.0_421 Maven信息:Apache Maven 3.9.9

运行方式和报错信息可见于该项目的README文档。

CodePlayer commented 3 days ago

此时,你可以在 readerFeatures 数组中加一个 JSONReader.Feature.SupportAutoType,就不会报错了。

  1. Fastjson 2 的 autoTypeFilter 目前对 嵌套泛型Map 的 @type 支持似乎还有些问题,不够完善,可以 @wenshao
  2. 你这个场景,其实可以不需要设置 WriteClassName,因为你已经手动指定了反序列化的对象类型。
  3. 如果JSON来源可控,也可以启用 SupportAutoType 这个枚举(记住,不要全局开启)。
fwhdzh commented 3 days ago

感谢您的回复!我在加上了JSONReader.Feature.SupportAutoType后,该测试用例可以正常通过。

不过当前的FastJson2版本中,直接将SupportAutoType标记为弃用感觉很容易让人产生误解。基于当前的该枚举注释所提供的信息以及其他issue(#217 )中的讨论,我将其误解为了目前"在任何情况下都应该尽量避免使用该枚举,同时仅使用AutoTypeBeforeHandler即可以在不使用该枚举的情况下完成预期的功能"。

是否可以考虑重构下这部分代码,比如在使用AutoTypeBeforeHandler时,自动在对应的局部反序列化过程中调用该枚举。或者将当前的该注解的@Deprecated注解去除并仅通过注释来进行警告?