【问题标题】:GSON won't properly serialise a class that extends HashMapGSON 不会正确序列化扩展 HashMap 的类
【发布时间】:2014-02-22 20:56:35
【问题描述】:

我有以下代码:

public static class A
{
    public A() {}
    private List<B> bs = new ArrayList<B>();
    public List<B> getBs() {
        return bs;
    }
    public void setBs(List<B> bs) {
        this.bs = bs;
    }
}

public static class B
{
    B(String foo){this.foo=foo;}
    private String foo;
    public String getFoo() {
        return foo;
    }
    public void setFoo(String foo) {
        this.foo = foo;
    }
}

public static void main(String[] args) throws Exception {
    Gson gson = new Gson();
    A a = new A();
    a.getBs().add(new B("bar"));
    System.out.println(gson.toJson(a));
}

正如预期的那样,输出是:

{"bs":[{"foo":"bar"}]}

但是,如果我将 A 设为 HashMap 的子类:

public static class A extends HashMap

我得到一个空集返回:{}

我什至尝试过:

System.out.println(gson.toJson(a, new TypeToken<A>(){}.getType()));

和:

System.out.println(gson.toJson(a, new TypeToken<HashMap>(){}.getType()));

谁能告诉我是否/如何使用 GSON 序列化这个 HashMap 子类?

【问题讨论】:

标签: java gson


【解决方案1】:

Gson 使用(默认和自定义)TypeAdapterFactory 实例和它们创建的 TypeAdapter 对象来序列化/反序列化您的对象。

它遍历已注册的TypeAdapterFactory 对象列表,并选择第一个可以为您提供的对象类型创建适当TypeAdapter 的对象。这些TypeAdapterFactory 对象之一是MapTypeAdapterFactory 类型之一,它创建基于java.util.Map 接口(键/值)序列化/反序列化的TypeAdapterMapTypeAdapterFactory$Adapter 类型)。它对您的自定义子类型的字段没有任何作用。

如果您希望 Gson 将您的类型序列化为 Map 和自定义类型,则需要直接注册自定义 TypeAdapter 或创建 TypeAdapter 对象的自定义 TypeAdapterFactory

【讨论】:

    【解决方案2】:

    这是自定义的 TypeAdapterFactory。

    测试:

    public static void main(String[] args) throws Exception{
        Gson gson = new GsonBuilder()
                .registerTypeAdapterFactory(new RetainFieldMapFactory())
                .create();
    
        Foo f = gson.fromJson("{'key1':'value1','key2':'value2'}", Foo.class);
    
        System.out.println("in map:\t" + f.toString());
        System.out.println("f.key1:\t"+f.key1);
        System.out.println("toJson:\t"+gson.toJson(f));
    }
    
    public static class Foo extends HashMap<String, String> {
        private String key1;
    }
    

    输出:

    in map: {key2=value2}
    f.key1: value1
    toJson: {"key2":"value2","key1":"value1"}
    

    RetainFieldMapFactory.java:

    /**
     * Created by linfaxin on 2015/4/9 009.
     * Email: linlinfaxin@163.com
     */
    public class RetainFieldMapFactory implements TypeAdapterFactory {
    
        FieldNamingPolicy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
        ConstructorConstructor constructorConstructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>>emptyMap());
        MapTypeAdapterFactory defaultMapFactory = new MapTypeAdapterFactory(constructorConstructor, false);
        ReflectiveFilterMapFieldFactory defaultObjectFactory = new ReflectiveFilterMapFieldFactory(constructorConstructor,
                fieldNamingPolicy, Excluder.DEFAULT);
    
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            final TypeAdapter<T> mapAdapter = defaultMapFactory.create(gson, type);
            if(mapAdapter!=null){
                return (TypeAdapter<T>) new RetainFieldMapAdapter(mapAdapter, defaultObjectFactory.create(gson, type));
            }
            return mapAdapter;
        }
    
    
        class RetainFieldMapAdapter extends TypeAdapter<Map<String, Object>>{
            TypeAdapter<Map<String, Object>> mapAdapter;
            ReflectiveTypeAdapterFactory.Adapter<Map<String, Object>> objectAdapter;
            RetainFieldMapAdapter(TypeAdapter mapAdapter, ReflectiveTypeAdapterFactory.Adapter objectAdapter) {
                this.mapAdapter = mapAdapter;
                this.objectAdapter = objectAdapter;
            }
    
            @Override
            public void write(final JsonWriter out, Map<String, Object> value) throws IOException {
                //1.write object
                StringWriter sw = new StringWriter();
                objectAdapter.write(new JsonWriter(sw), value);
    
                //2.convert object to a map
                Map<String, Object> objectMap = mapAdapter.fromJson(sw.toString());
    
                //3.overwrite fields in object to a copy map
                value = new LinkedHashMap<String, Object>(value);
                value.putAll(objectMap);
    
                //4.write the copy map
                mapAdapter.write(out, value);
            }
    
            @Override
            public Map<String, Object> read(JsonReader in) throws IOException {
                //1.create map, all key-value retain in map
                Map<String, Object> map = mapAdapter.read(in);
    
                //2.create object from created map
                Map<String, Object> object = objectAdapter.fromJsonTree(mapAdapter.toJsonTree(map));
    
                //3.remove fields in object from map
                for(String field : objectAdapter.boundFields.keySet()){
                    map.remove(field);
                }
                //4.put map to object
                object.putAll(map);
                return object;
            }
        }
    
        /**
         * If class is extends from some custom map,
         * class should implement this to avoid serialize custom map's fields
         */
        public interface RetainFieldFlag {}
    
        static class ReflectiveFilterMapFieldFactory extends ReflectiveTypeAdapterFactory{
            public ReflectiveFilterMapFieldFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
                super(constructorConstructor, fieldNamingPolicy, excluder);
            }
    
            @Override
            protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw) {
                if(RetainFieldFlag.class.isAssignableFrom(originalRaw)){
                    return RetainFieldFlag.class.isAssignableFrom(willFindClass);
                }else{
                    Class[] endClasses = new Class[]{Object.class, HashMap.class, LinkedHashMap.class,
                            LinkedTreeMap.class, Hashtable.class, TreeMap.class, ConcurrentHashMap.class,
                            IdentityHashMap.class, WeakHashMap.class, EnumMap.class};
                    for(Class c : endClasses){
                        if(willFindClass == c) return false;
                    }
                }
                return super.shouldFindFieldInClass(willFindClass, originalRaw);
            }
        }
        /**
         * below code copy from {@link com.google.gson.internal.bind.ReflectiveTypeAdapterFactory}
         * (little modify, in source this class is final)
         * Type adapter that reflects over the fields and methods of a class.
         */
        static class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
            private final ConstructorConstructor constructorConstructor;
            private final FieldNamingStrategy fieldNamingPolicy;
            private final Excluder excluder;
    
            public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
                                                FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
                this.constructorConstructor = constructorConstructor;
                this.fieldNamingPolicy = fieldNamingPolicy;
                this.excluder = excluder;
            }
    
            public boolean excludeField(Field f, boolean serialize) {
                return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
            }
    
            private String getFieldName(Field f) {
                SerializedName serializedName = f.getAnnotation(SerializedName.class);
                return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
            }
    
            public <T> Adapter<T> create(Gson gson, final TypeToken<T> type) {
                Class<? super T> raw = type.getRawType();
    
                if (!Object.class.isAssignableFrom(raw)) {
                    return null; // it's a primitive!
                }
    
                ObjectConstructor<T> constructor = constructorConstructor.get(type);
                return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
            }
    
            private ReflectiveTypeAdapterFactory.BoundField createBoundField(
                    final Gson context, final Field field, final String name,
                    final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
                final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
    
                // special casing primitives here saves ~5% on Android...
                return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
                    final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
                    @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
                    @Override void write(JsonWriter writer, Object value)
                            throws IOException, IllegalAccessException {
                        Object fieldValue = field.get(value);
                        TypeAdapter t = new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
                        t.write(writer, fieldValue);
                    }
                    @Override void read(JsonReader reader, Object value)
                            throws IOException, IllegalAccessException {
                        Object fieldValue = typeAdapter.read(reader);
                        if (fieldValue != null || !isPrimitive) {
                            field.set(value, fieldValue);
                        }
                    }
                };
            }
    
            private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
                Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
                if (raw.isInterface()) {
                    return result;
                }
    
                Type declaredType = type.getType();
                Class<?> originalRaw = type.getRawType();
                while (shouldFindFieldInClass(raw, originalRaw)) {
                    Field[] fields = raw.getDeclaredFields();
                    for (Field field : fields) {
                        boolean serialize = excludeField(field, true);
                        boolean deserialize = excludeField(field, false);
                        if (!serialize && !deserialize) {
                            continue;
                        }
                        field.setAccessible(true);
                        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
                        BoundField boundField = createBoundField(context, field, getFieldName(field),
                                TypeToken.get(fieldType), serialize, deserialize);
                        BoundField previous = result.put(boundField.name, boundField);
                        if (previous != null) {
                            throw new IllegalArgumentException(declaredType
                                    + " declares multiple JSON fields named " + previous.name);
                        }
                    }
                    type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
                    raw = type.getRawType();
                }
                return result;
            }
            protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw){
                return willFindClass != Object.class;
            }
    
            static abstract class BoundField {
                final String name;
                final boolean serialized;
                final boolean deserialized;
    
                protected BoundField(String name, boolean serialized, boolean deserialized) {
                    this.name = name;
                    this.serialized = serialized;
                    this.deserialized = deserialized;
                }
    
                abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
                abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
            }
    
            public static final class Adapter<T> extends TypeAdapter<T> {
                private final ObjectConstructor<T> constructor;
                private final Map<String, BoundField> boundFields;
    
                private Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
                    this.constructor = constructor;
                    this.boundFields = boundFields;
                }
    
                @Override public T read(JsonReader in) throws IOException {
                    if (in.peek() == JsonToken.NULL) {
                        in.nextNull();
                        return null;
                    }
    
                    T instance = constructor.construct();
    
                    try {
                        in.beginObject();
                        while (in.hasNext()) {
                            String name = in.nextName();
                            BoundField field = boundFields.get(name);
                            if (field == null || !field.deserialized) {
                                in.skipValue();
                            } else {
                                field.read(in, instance);
                            }
                        }
                    } catch (IllegalStateException e) {
                        throw new JsonSyntaxException(e);
                    } catch (IllegalAccessException e) {
                        throw new AssertionError(e);
                    }
                    in.endObject();
                    return instance;
                }
    
                @Override public void write(JsonWriter out, T value) throws IOException {
                    if (value == null) {
                        out.nullValue();
                        return;
                    }
    
                    out.beginObject();
                    try {
                        for (BoundField boundField : boundFields.values()) {
                            if (boundField.serialized) {
                                out.name(boundField.name);
                                boundField.write(out, value);
                            }
                        }
                    } catch (IllegalAccessException e) {
                        throw new AssertionError();
                    }
                    out.endObject();
                }
            }
        }
    
    
        static class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
            private final Gson context;
            private final TypeAdapter<T> delegate;
            private final Type type;
    
            TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
                this.context = context;
                this.delegate = delegate;
                this.type = type;
            }
    
            @Override
            public T read(JsonReader in) throws IOException {
                return delegate.read(in);
            }
    
            @SuppressWarnings({"rawtypes", "unchecked"})
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                // Order of preference for choosing type adapters
                // First preference: a type adapter registered for the runtime type
                // Second preference: a type adapter registered for the declared type
                // Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
                // Fourth preference: reflective type adapter for the declared type
    
                TypeAdapter chosen = delegate;
                Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
                if (runtimeType != type) {
                    TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
                    if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
                        // The user registered a type adapter for the runtime type, so we will use that
                        chosen = runtimeTypeAdapter;
                    } else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
                        // The user registered a type adapter for Base class, so we prefer it over the
                        // reflective type adapter for the runtime type
                        chosen = delegate;
                    } else {
                        // Use the type adapter for runtime type
                        chosen = runtimeTypeAdapter;
                    }
                }
                chosen.write(out, value);
            }
    
            /**
             * Finds a compatible runtime type if it is more specific
             */
            private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
                if (value != null
                        && (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
                    type = value.getClass();
                }
                return type;
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多