Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.2.76中BugFix#3693的修复代码又引入了新的Bug! #3810

Closed
darren-wang opened this issue Jun 15, 2021 · 6 comments · Fixed by #3826
Closed

1.2.76中BugFix#3693的修复代码又引入了新的Bug! #3810

darren-wang opened this issue Jun 15, 2021 · 6 comments · Fixed by #3826

Comments

@darren-wang
Copy link

darren-wang commented Jun 15, 2021

#3693

上面的 #3639 提出了一个bug,即通过@JSONField#deserializeUsing属性指定的自定义反序列化器被后续的代码覆盖,并在1.2.76中进行了修复,然而我们在使用升级后的1.2.76时发现1.2.75中没有问题的代码出现报错,经定位是该Issue的解决方案引入了新的Bug。

我们的代码是构造了一个ClassA<ClassB<String>>这种样式的类,ClassA<T>的声明中有一个字段T data,实际类型是ClassB<String>,现在1.2.76的修复方案,虽然确保了@JSONField#deserializeUsing属性指定的自定义反序列化器具有最高的优先级,但是在我们的场景下,并没有使用@JSONField注解,原本ClassAT data字段的反序列化器应该是针对运行时实际类型ClassB<String>创建的,现在却获取到的是一个普通的Object的通用反序列化器。

具体代码如下,1.2.76中:

    public ObjectDeserializer getFieldValueDeserilizer(ParserConfig config) {
        if (fieldValueDeserilizer == null) {
            JSONField annotation = fieldInfo.getAnnotation();
            if (annotation != null && annotation.deserializeUsing() != Void.class) {
                Class<?> deserializeUsing = annotation.deserializeUsing();
                try {
                    fieldValueDeserilizer = (ObjectDeserializer) deserializeUsing.newInstance();
                } catch (Exception ex) {
                    throw new JSONException("create deserializeUsing ObjectDeserializer error", ex);
                }
            } else {
                fieldValueDeserilizer = config.getDeserializer(fieldInfo.fieldClass, fieldInfo.fieldType);
            }
        }

        return fieldValueDeserilizer; // 这个成员变量和返回值不为空
    }

这个方法无论成员变量fieldValueDeserilizer初始的状态是否为null,该方法执行后,成员变量fieldValueDeserilizer都会被转为非null的值,考虑了通过@JSONField注解自定义反序列化器的场景。

但是在下面的方法中:

@Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        if (this.fieldValueDeserilizer == null) {
            getFieldValueDeserilizer(parser.getConfig());
        }

        ObjectDeserializer fieldValueDeserilizer = this.fieldValueDeserilizer;
        Type fieldType = fieldInfo.fieldType;
        if (objectType instanceof ParameterizedType) {
            ParseContext objContext = parser.getContext();
            if (objContext != null) {
                objContext.type = objectType;
            }
            if (fieldType != objectType) {
                fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType);
                if (fieldValueDeserilizer == null) { // 该处修改导致下面一行失效
                    fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType); // 代码失效,原有逻辑无法正确执行
                }
            }
        }

1.2.76中的67行进行了一个null判断,但是ObjectDeserializer fieldValueDeserilizer = this.fieldValueDeserilizer即局部变量fieldValueDeserilizer正是成员变量this.fieldValueDeserilizer,而后者如上面所说的,在该方法调用上面的getFieldValueDeserilizer之后永远不会为null,所以导致68行代码不会被执行,而之前的版本能够处理这种嵌套的场景,原因就在于68行获取了一个与实际运行时类型匹配的反序列化器,现在这样的修复方案使得68行代码失效了,从而嵌套泛型的场景下失效,将给现有生产代码造成巨大影响

@timandy
Copy link
Contributor

timandy commented Jun 16, 2021

提供下测试用例?

@darren-wang
Copy link
Author

我们本身的场景是对Spring MVC的@RequestBody的处理逻辑进行了扩展,里面的例子跟Spring框架绑定太严,不便于操作,就写了一个简单的,问题同样存在:

package com.dw.infinite.junit5;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.reflect.TypeToken;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Type;

public class FastJsonTest {
    @Data
    static class TestA<T> {
        int a1;
        T a2;
    }
    @Data
    static class TestB<T> {
        String b1;
        T b2;
    }

    @Test
    void test() throws JsonProcessingException {
        String json = "{\"a1\":99,\"a2\":{\"b1\":\"b1\",\"b2\":\"b2\"}}";

        TestA<?> ret1 = null;
        TestA<?> ret2 = null;

        TypeToken<TestB<String>> tt = new TypeToken<TestB<String>>() {};
        Type bType = tt.getType();
        Type realType = new ParameterizedTypeImpl(new Type[]{ bType }, TestA.class, TestA.class);

        JSONObject jo = JSONObject.parseObject(json);
        ret1 = jo.toJavaObject(realType);
        ret2 = JSON.parseObject(json, realType);

        System.out.println(ret1.getA2().getClass().getName());
        System.out.println(ret2.getA2().getClass().getName());
    }
}

同一段代码,1.2.76的输出是,通过调试也能看到,这个结果跟上一个版本不一致,也不是我们要的结果:

com.alibaba.fastjson.JSONObject
com.alibaba.fastjson.JSONObject

1.2.75的输出是正确的,通过调试跟进去看也能发现类型是对的:

com.dw.infinite.junit5.FastJsonTest$TestB
com.dw.infinite.junit5.FastJsonTest$TestB

另外你看我贴的代码,1.2.76里面的代码实现是有点问题的,这块具体代码我不是特别清楚,但是看

                if (fieldValueDeserilizer == null) { // 该处修改导致下面一行失效,感觉这个值可能恒不为null
                    fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType); // 代码失效,原有逻辑无法正确执行
                }

这一块觉得代码块里的代码可能会失效,实际上我们的场景中1.2.76的代码就失效了。

@darren-wang
Copy link
Author

提供下测试用例?

已提供,请参考。

@hnyyghk
Copy link
Contributor

hnyyghk commented Jun 19, 2021

我也遇到了这样的问题,且由于受到TypeReference的缓存影响,该问题十分隐蔽且难以复现
测试用例如下,请参考~

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import lombok.Data;
import org.junit.Test;

import java.lang.reflect.Type;
import java.util.List;

public class FastJsonTest {
    @Data
    static class TestA<T> {
        T a;
    }

    @Data
    static class TestB {
        String b;
    }

    private final static String json = "{\"a\":[{\"b\":\"b\"}]}";

    /**
     * DefaultFieldDeserializer的parseField方法在1.2.76中67行引入了一个null判断,修复了@JSONField注解自定义反序列化器失效的问题#3693,
     * 但对于没有使用这个注解自定义反序列化器的情况,原本是通过68行获取实际运行时类型fieldType匹配的反序列化器,
     * 由于通过55行getFieldValueDeserilizer始终能获取到默认的普通Object通用反序列化器,null判断始终为false使这个逻辑失效了,
     * 从而导致反序列化使用了默认的普通Object通用反序列化器,将list反序列化为JSONArray,将对象反序列化为JSONObject,
     * 后续逻辑处理就会抛出java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to ...
     *
     * @see com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer
     */
//    @Test
    public void testError() {
        System.out.println("---------------testError---------------");
        ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{TestB.class}, List.class, List.class);
        ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, TestA.class, TestA.class);
        JSONObject jo = JSONObject.parseObject(json);
        //为了打印出b的className这里将返回值改为TestA<List<?>>,否则会抛出ClassCastException
        TestA<List<?>> ret = jo.toJavaObject(outer);

        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }

    /**
     * 且由于受到TypeReference的缓存影响,该问题十分隐蔽且难以复现
     * 如果先调用错误方法,后续即使调用相同泛型的正确方法也会全部解析报错
     * 如果先调用正确方法,后续调用相同泛型的正确方法就不会出现问题
     *
     * @see com.alibaba.fastjson.TypeReference
     */
    @Test
    public void testErrorFirst() {
        testError();
        testOk();
    }

    @Test
    public void testOkFirst() {
        testOk();
        testError();
        testOk();
    }

//    @Test
    public void testOk() {
        testOk1();
        testOk2();
    }

//    @Test
    public void testOk1() {
        System.out.println("---------------testOk1---------------");
        TestA<List<?>> ret = JSON.parseObject(json, new TypeReference<TestA<List<TestB>>>() {
        }.getType());
        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }

//    @Test
    public void testOk2() {
        System.out.println("---------------testOk2---------------");
        ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{TestB.class}, List.class, List.class);
        ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, TestA.class, TestA.class);
        TestA<List<?>> ret = JSONObject.parseObject(json, outer);
        System.out.println("a class name: " + ret.getA().getClass().getName());
        System.out.println("b class name: " + ret.getA().get(0).getClass().getName());
    }
}

@darren-wang
Copy link
Author

提供下测试用例?

您好,现在有在修复吗?

@timandy
Copy link
Contributor

timandy commented Jun 29, 2021

提供下测试用例?

您好,现在有在修复吗?

@darren-wang soory, 最近工作太忙了, 你有兴趣直接发个 pr 吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants