Skip to content
This repository has been archived by the owner on Oct 23, 2024. It is now read-only.

对泛型的具化衍生类支持有问题 #3730

Open
ayanamist opened this issue Apr 21, 2021 · 8 comments
Open

对泛型的具化衍生类支持有问题 #3730

ayanamist opened this issue Apr 21, 2021 · 8 comments

Comments

@ayanamist
Copy link

ayanamist commented Apr 21, 2021

和gson、jackson的对比如下代码。

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.3</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
package com.ayanamist;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

public class Test {
    public static void main(String[] args) throws Exception {
        Derived d = new Derived();
        d.put("k", Collections.singletonList(new DAO()));
        String str = com.alibaba.fastjson.JSON.toJSONString(d);
        System.out.printf("Str: %s\n", str);

        com.google.gson.Gson gson = new com.google.gson.Gson();
        d = gson.fromJson(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "gson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
        d = objectMapper.readValue(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "jackson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        d = com.alibaba.fastjson.JSON.parseObject(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").get(0).getClass().getSimpleName());
    }

    public static class DAO {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    public static class Derived extends HashMap<String, List<DAO>> {
    }
}

输出结果:

Str: {"k":[{"aaa":"ccc","bbb":2}]}
gson: DAO
jackson: DAO
Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.ayanamist.Test$DAO
	at com.ayanamist.Test.main(Test.java:24)

可以看到gson和jackson都能正确的反序列化这个类,但fastjson却反序列化成了JSONObject,导致抛出异常

Certseeds added a commit to Certseeds/fastjson that referenced this issue Apr 21, 2021
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
Certseeds added a commit to Certseeds/fastjson that referenced this issue Apr 21, 2021
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
@Certseeds
Copy link
Contributor

奇怪的是,非inner public static class居然parseObject就会报错

@ayanamist
Copy link
Author

奇怪的是,非inner public static class居然parseObject就会报错

没错,也是fastjson才会有的报错

@ayanamist
Copy link
Author

给一个workaround,可以绕过这个问题

package com.ayanamist;

import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class Test {
    public static void main(String[] args) throws Exception {
        Derived d = new Derived();
        d.put("k", Collections.singletonList(new DAO()));
        String str = com.alibaba.fastjson.JSON.toJSONString(d);
        System.out.printf("Str: %s\n", str);

        com.google.gson.Gson gson = new com.google.gson.Gson();
        d = gson.fromJson(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "gson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
        d = objectMapper.readValue(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "jackson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        // workaround https://github.com/alibaba/fastjson/issues/3730
        ParserConfig.getGlobalInstance().putDeserializer(Derived.class, DerivedDeserializer.instance);

        d = com.alibaba.fastjson.JSON.parseObject(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").get(0).getClass().getSimpleName());
    }

    public static class DAO {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    // @JSONType(deserializer = DerivedDeserializer.class) is useless, since com.alibaba.fastjson.parser.ParserConfig.getDeserializer will use MapDeserializer first
    public static class Derived extends HashMap<String, List<DAO>> {
        public Derived() {
            super();
        }

        public Derived(Map<String, List<DAO>> object) {
            super(object);
        }
    }

    public static class DerivedDeserializer implements ObjectDeserializer {
        public static final DerivedDeserializer instance = new DerivedDeserializer();

        private static final TypeReference<HashMap<String, List<DAO>>> TYPE_REFERENCE = new TypeReference<HashMap<String, List<DAO>>>() {
        };

        @SuppressWarnings("unchecked")
        @Override public Derived deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
            return new Derived(parser.parseObject(TYPE_REFERENCE.getType()));
        }

        @Override public int getFastMatchToken() {
            return JSONToken.LBRACE;
        }
    }
}

@ayanamist
Copy link
Author

ayanamist commented Apr 22, 2021

跟了一下代码,问题出在 com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.util.Map, int) 这个方法的第一句 if (type instanceof ParameterizedType) {
因为传的是派生的类,所以这里是false,导致跳过了参数化解析的过程,稍微改一下就能解决这个问题,同时避免上面workaround中带来的多一次构造器拷贝的性能开销:

package com.ayanamist;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.MapDeserializer;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class Test {
    public static void main(String[] args) throws Exception {
        Derived d = new Derived();
        d.put("k", Collections.singletonList(new DAO()));
        String str = com.alibaba.fastjson.JSON.toJSONString(d);
        System.out.printf("Str: %s\n", str);

        com.google.gson.Gson gson = new com.google.gson.Gson();
        d = gson.fromJson(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "gson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
        d = objectMapper.readValue(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "jackson: %s\n", d.get("k").get(0).getClass().getSimpleName());

        // workaround https://github.com/alibaba/fastjson/issues/3730
        ParserConfig.getGlobalInstance().putDeserializer(Derived.class, GenericMapSuperclassDeserializer.instance);

        d = com.alibaba.fastjson.JSON.parseObject(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").get(0).getClass().getSimpleName());
    }

    public static class DAO {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    // @JSONType(deserializer = DerivedDeserializer.class) is useless, since com.alibaba.fastjson.parser.ParserConfig.getDeserializer will use MapDeserializer first
    public static class Derived extends HashMap<String, List<DAO>> {
    }

    private static class GenericMapSuperclassDeserializer implements ObjectDeserializer {
        public static final GenericMapSuperclassDeserializer instance = new GenericMapSuperclassDeserializer();

        @SuppressWarnings("unchecked")
        @Override public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
            Class<?> clz = (Class<?>) type;
            Type genericSuperclass = clz.getGenericSuperclass();
            Map map;
            try {
                map = (Map) clz.newInstance();
            } catch (Exception e) {
                throw new JSONException("unsupport type " + type, e);
            }
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            Type keyType = parameterizedType.getActualTypeArguments()[0];
            Type valueType = parameterizedType.getActualTypeArguments()[1];
            if (String.class == keyType) {
                return (T) MapDeserializer.parseMap(parser, (Map<String, Object>) map, valueType, fieldName, 0);
            } else {
                return (T) MapDeserializer.parseMap(parser, map, keyType, valueType, fieldName);
            }
        }

        @Override public int getFastMatchToken() {
            return JSONToken.LBRACE;
        }
    }
}

Certseeds added a commit to Certseeds/fastjson that referenced this issue Apr 22, 2021
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
@hotaru555
Copy link
Contributor

hotaru555 commented Apr 23, 2021

这个应该是一个feature。在 #3448 #1583 中提到过相似的问题,已经被修复。在map类型的反序列化中,如果value是list类型(用“[”括起),在结果对象中的value会是JSONArray类型。需要用户自己做后续工作。

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

public class test {
    public static void main(String[] args) throws Exception {
        Derived d = new Derived();
        d.put("k", Collections.singletonList(new DAO()));
        String str = com.alibaba.fastjson.JSON.toJSONString(d);
        System.out.printf("Str: %s\n", str);


        d = com.alibaba.fastjson.JSON.parseObject(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").getClass().getSimpleName());

        Object data = d.get("k");
        List<DAO> list = null;
        list = JSONObject.parseArray(((JSONArray) data).toJSONString(), DAO.class);
        assert list != null;
        System.out.println(list.get(0).getClass().getSimpleName());

    }

    public static class DAO {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    public static class Derived extends HashMap<String, List<DAO>> {
    }
}

结果是

Str: {"k":[{"aaa":"ccc","bbb":2}]}
gson: DAO
jackson: DAO
fastjson: JSONArray
DAO

@ayanamist
Copy link
Author

这个应该是一个feature。在 #3448 #1583 中提到过相似的问题,已经被修复。在map类型的反序列化中,如果value是list类型(用“[”括起),在结果对象中的value会是JSONArray类型。需要用户自己做后续工作。

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

public class test {
    public static void main(String[] args) throws Exception {
        Derived d = new Derived();
        d.put("k", Collections.singletonList(new DAO()));
        String str = com.alibaba.fastjson.JSON.toJSONString(d);
        System.out.printf("Str: %s\n", str);


        d = com.alibaba.fastjson.JSON.parseObject(str, Derived.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").getClass().getSimpleName());

        Object data = d.get("k");
        List<DAO> list = null;
        list = JSONObject.parseArray(((JSONArray) data).toJSONString(), DAO.class);
        assert list != null;
        System.out.println(list.get(0).getClass().getSimpleName());

    }

    public static class DAO {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    public static class Derived extends HashMap<String, List<DAO>> {
    }
}

结果是

Str: {"k":[{"aaa":"ccc","bbb":2}]}
gson: DAO
jackson: DAO
fastjson: JSONArray
DAO

我说的不是JsonArray的事情,是array里面不是要的对象,而是JsonObject,而传过去的对象类型明明已经明确了具体的类。

@Certseeds
Copy link
Contributor

package com.alibaba.json.bvt.issue_3700;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;

public class Issue3730_3 {
    private static final String test_str = "{\"k\":{\"aaa\":\"ccc\",\"bbb\":2}}";
    private static final String test_str2 = "{\"k\":[{\"aaa\":\"ccc\",\"bbb\":2}]}";

    @Test
    public void test_for_issue_parse_output_PublicstaticKV_1() {
        Issue3730_2DerivedPublicStaticKV d = parseToMap(test_str, String.class, Issue3730_2DAOPublicStatic.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").getClass().getSimpleName());
    }

    @Test
    public void test_for_issue_parse_output_Publicstatic_KV2() {
        Issue3730_2DerivedPublicStaticKV2 d = parseToMap2(test_str2, String.class, Issue3730_2DAOPublicStatic.class);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").getClass().getSimpleName());
    }

    @Test
    public void test_for_issue_parse_output_Publicstatic_KV2_warp() {
        Issue3730_2DerivedPublicStaticKV2<String, Issue3730_2DAOPublicStatic> d = parseToMap2_warp(test_str2);
        System.out.printf(Locale.ENGLISH, "fastjson: %s\n", d.get("k").getClass().getSimpleName());
    }

    public static <K, V> Issue3730_2DerivedPublicStaticKV<K, V> parseToMap(String json,
                                                                           Class<K> keyType,
                                                                           Class<V> valueType) {
        return JSON.parseObject(json,
                new TypeReference<Issue3730_2DerivedPublicStaticKV<K, V>>(keyType, valueType) {
                }.getType());
    }

    public static <K, V> Issue3730_2DerivedPublicStaticKV2<String, Issue3730_2DAOPublicStatic> parseToMap2_warp(String json) {
        return JSON.parseObject(json,
                new TypeReference<Issue3730_2DerivedPublicStaticKV2<K, List<V>>>(String.class, Issue3730_2DAOPublicStatic.class) {
                }.getType());
    }

    public static <K, V> Issue3730_2DerivedPublicStaticKV2<K, V> parseToMap2(String json,
                                                                             Class<K> keyType,
                                                                             Class<V> valueType) {
        return JSON.parseObject(json,
                new TypeReference<Issue3730_2DerivedPublicStaticKV2<K, List<V>>>(keyType, valueType) {
                }.getType());
    }
    public static class Issue3730_2DAOPublicStatic {
        private String aaa = "ccc";
        private int bbb = 2;

        public String getAaa() {
            return aaa;
        }

        public void setAaa(String aaa) {
            this.aaa = aaa;
        }

        public int getBbb() {
            return bbb;
        }

        public void setBbb(int bbb) {
            this.bbb = bbb;
        }
    }

    public static class Issue3730_2DerivedPublicStaticKV<K, V> extends HashMap<K, V> {
    }

    public static class Issue3730_2DerivedPublicStaticKV2<K, V> extends HashMap<K, List<V>> {
    }
}

感觉关键在于给的类型信息太少了,参考wiki-typereference写出来的就可以

@Certseeds
Copy link
Contributor

#1097 看起来很相似

Certseeds added a commit to Certseeds/fastjson that referenced this issue Jun 26, 2022
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
Certseeds added a commit to Certseeds/fastjson that referenced this issue Jun 26, 2022
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
Certseeds added a commit to Certseeds/fastjson that referenced this issue Jun 26, 2022
</subject>

Branch: issue3730

<type>:
- [ ] Bug fix
- [ ] Bug fix (Test)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] This change requires a documentation update

<body>

<footer>

Signed-off-by: Certseeds <51754303+Certseeds@users.noreply.github.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants