美文网首页
Gson如何解决可变类型集合(Map/List)序列化

Gson如何解决可变类型集合(Map/List)序列化

作者: supylc | 来源:发表于2022-01-28 16:12 被阅读0次

Gson的序列化功能

  • 支持int, long, bool, string等基本类型
  • 支持Url, 数组, atomic, Bean等各种类型
  • List, Map类型也支持泛型
  • 支持数值Number的转换规则
  • 支持属性命名下划线/驼峰
  • 支持过滤某写属性
  • 支持自定义序列化adapter

遇到的问题

尽管gson足够强大, 但是还是遇到了问题

譬如:

Map<String, Object> map = new HashMap<>();
map.put("int", 22);
map.put("long", 2211L);
float flo = 333.666f;
map.put("float", flo);
double dou = 122.55f;
map.put("double", dou);
map.put("bean", new TestBean("desc"));
String json = new Gson.toJson(map);
Map<String, Object> map2 = new Gson.fromJson(json, HashMap.class);

经过序列化/反序列化,返回的map2, 如下效果:


16433549481788.jpg

可见, int, long, float全都转成double; 而bean类被转成LinkTreeMap

同理, list也是一样的问题;

解决

可不可通过自定义gson序列化解决? 答案是可以的

通过自定义Gson gson = new GsonBuilder(), 然后设置
.registerTypeHierarchyAdapter(Map.class, new TypeAdapter<Map<String, Object>>()

.registerTypeHierarchyAdapter(Map.class, new TypeAdapter<Collection<String, Object>>()
即可

基本原理

定制gson的typeAdapter, 写入时在数据头部插入map/list的自身和item的class字典信息, 然后解析的时候解析字典, 反序列化正确的类型.

代码如下

/**
 * Created by Supylc on 2022/1/25.
 * 自定义gson,功能为:
 * 1、能解析可变类型的Map, 例如Map<String, Object>
 * 2、能正确解析Map里面的float,double,long,int,short,byte,char等
 * 3、需要用GsonCasual类定制的Gson才能正确解析,如果在本类写入map,又用外部gson读,则维持原解析效果
 *
 * 适用场景:
 * 需要用到map或list序列化的地方(特别是item为可变类型)
 *
 * 注意:
 * 用GsonCasual写和读,不要出现用GsonCasual写然后用其他Gson读(反之亦然)读情况
 */
public class GsonCasual {

    private static final String TAG = "GsonCasual";
    private static final String DICT = "__clz_dict__";
    private static final Map<String, Class<?>> mClazzCacheMap = new HashMap<>();

    /**
     * 设置支持map和list的准确解析,
     * 原理:
     * 1、自定义读写map和list
     * 2、在写json的头部,插入class字典,包含Map(或List)和key-value(或list-item)的类型信息
     *
     * 字典格式为:
     * 数组大小,map(list)的类型class,value(map或list的item)的类型class
     * 如:5,java.util.HashMap, java.lang.long, java.lang.Integer, java.lang.String
     *
     * 把字典信息独立放在头部,对旧的map数据解析不做修改,容错性更好(比如不知情的情况下,用不同的gson进行读写,也不报错)
     */
    private static final Gson gson = new GsonBuilder()
            //处理所有的map类型
            .registerTypeHierarchyAdapter(Map.class, new TypeAdapter<Map<String, Object>>() {

                @Override
                public void write(JsonWriter out, Map<String, Object> value) throws IOException {
                    if (value == null) {
                        out.nullValue();
                        return;
                    }
                    out.beginObject();
                    Set<Map.Entry<String, Object>> entrySet = value.entrySet();
                    Object entryValue;
                    //在头部写一个class字典数据
                    writeClassDict(out, value);
                    for (Map.Entry<String, Object> entry: entrySet) {
                        entryValue = entry.getValue();
                        if (entryValue == null) {
                            out.name(entry.getKey()).nullValue();
                        } else {
                            out.name(entry.getKey()).jsonValue(gson.toJson(entryValue));
                        }
                    }
                    out.endObject();
                }

                @Override
                public Map<String, Object> read(JsonReader in) throws IOException {
                    JsonToken jsonToken = in.peek();
                    if (jsonToken == JsonToken.NULL) {
                        return null;
                    }
                    Map<String, Object> result = null;
                    jsonToken = in.peek();
                    if (jsonToken != JsonToken.BEGIN_OBJECT) {
                        throw new NullPointerException("firstToken is wrong, gson invalid?");
                    }
                    in.beginObject();
                    int kvCount = 0;
                    String[] valueClazzArray = null;
                    while (in.hasNext()) {
                        String name = in.nextName();
                        Object readValue;
                        if (DICT.equals(name)) {
                            valueClazzArray = readDictObject(in);
                            continue;
                        }
                        if (result == null && valueClazzArray != null) {
                            result = (Map<String, Object>) newInstance(valueClazzArray[0]);
                        }
                        if (valueClazzArray != null) {
                            String valueClazz = valueClazzArray[kvCount + 1];
                            if (valueClazz == null) {
                                readValue = null;
                            } else {
                                readValue = gson.fromJson(in, getClazz(valueClazz));
                            }
                            kvCount ++;
                        } else {
                            readValue = gson.fromJson(in, String.class);
                        }

                        if (result == null) {
                            result = new HashMap<>();
                        }
                        result.put(name, readValue);
                    }
                    in.endObject();
                    if (result == null) {
                        result = new HashMap<>();
                    }
                    return result;
                }
            })
            //处理所有的集合类型
            .registerTypeHierarchyAdapter(Collection.class, new TypeAdapter<Collection<?>>() {

                @Override
                public void write(JsonWriter out, Collection<?> value) throws IOException {
                    if (value == null) {
                        out.nullValue();
                        return;
                    }
                    out.beginArray();
                    //在头部写一个class字典数据
                    writeClassDict(out, value);
                    for (Object entry: value) {
                        if (entry == null) {
                            out.nullValue();
                        } else {
                            out.jsonValue(gson.toJson(entry));
                        }
                    }
                    out.endArray();
                }

                @Override
                public Collection<?> read(JsonReader in) throws IOException {
                    JsonToken jsonToken = in.peek();
                    if (jsonToken == JsonToken.NULL) {
                        return null;
                    }
                    Collection<Object> result = null;

                    int kvCount = 0;
                    String[] valueClazzArray = null;
                    in.beginArray();
                    jsonToken = in.peek();
                    if (jsonToken == JsonToken.BEGIN_ARRAY) {
                        valueClazzArray = readDictObject(in);
                    }
                    while (in.hasNext()) {
                        Object readValue;
                        if (result == null && valueClazzArray != null) {
                            result = (Collection<Object>) newInstance(valueClazzArray[0]);
                        }
                        if (valueClazzArray != null) {
                            String valueClazz = valueClazzArray[kvCount + 1];
                            if (valueClazz == null) {
                                readValue = null;
                            } else {
                                readValue = gson.fromJson(in, getClazz(valueClazz));
                            }
                            kvCount ++;
                        } else {
                            readValue = gson.fromJson(in, String.class);
                        }

                        if (result == null) {
                            result = new ArrayList<>();
                        }
                        result.add(readValue);
                    }
                    in.endArray();
                    return result;
                }
            })
            .create();

    private static void writeClassDict(JsonWriter out, Object value) throws IOException {
        if (value instanceof Map) {
            Set<Map.Entry<String, Object>> entrySet = ((Map<String, Object>) value).entrySet();
            out.name(DICT);
            out.beginArray();
            out.value(entrySet.size() + 1);
            out.value(value.getClass().getName());
            for (Map.Entry<String, Object> entry: entrySet) {
                Object entryValue = entry.getValue();
                if (entryValue == null) {
                    out.nullValue();
                } else {
                    out.value(entryValue.getClass().getName());
                }
            }
            out.endArray();
        } else if (value instanceof Collection) {
            Collection<?> collection = (Collection<?>) value;
            out.beginArray();
            out.value(collection.size() + 1);
            out.value(value.getClass().getName());
            for (Object entry: collection) {
                if (entry == null) {
                    out.nullValue();
                } else {
                    out.value(entry.getClass().getName());
                }
            }
            out.endArray();
        }
    }

    private static String[] readDictObject(JsonReader in) throws IOException {
        int dictCount = 0;
        String[] valueClazzArray = null; //包含root类型以及item类型
        JsonToken jsonToken;
        in.beginArray();
        while (in.hasNext()) {
            if (dictCount > 1) {
                jsonToken = in.peek();
                if (jsonToken == JsonToken.NULL) {
                    in.nextNull();
                } else {
                    valueClazzArray[dictCount - 1] = in.nextString();
                }
            } else if (dictCount == 1) {
                valueClazzArray[0] = in.nextString();
            } else if (dictCount == 0) {
                valueClazzArray = new String[in.nextInt()];
            }
            dictCount++;
        }
        in.endArray();
        return valueClazzArray;
    }

    public static String toJson(Object src) {
        return gson.toJson(src);
    }

    public static <T> T fromJson(String json, Class<T> clazz) {
        return gson.fromJson(json, clazz);
    }

    private static Class<?> getClazz(String clazzName) {
        Class<?> clazz = mClazzCacheMap.get(clazzName);
        if (clazz == null) {
            try {
                clazz = Class.forName(clazzName);
            } catch (Exception e) {
                log("getClazz, not found class: " + clazzName);
                e.printStackTrace();
            }
        }
        return clazz;
    }

    private static Object newInstance(String clazz) {
        try {
            return getClazz(clazz).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void log(String log) {
        Log.i(TAG, log);
    }

用例

只需要用 GsonCasual.toJson/fromJson 即可, 使用也很简单

总结

  1. 原想用parcel方式解决, 因为parcel序列化更直接,更节省空间
    用记录class字典的方式, parcel也能实现, 但最后写出来, 其实也是走gson走过的路, 因此何必重复造轮子? 明显的, parcel不适合用在此场景

  2. 选择gson的自定义实现, 效率也很高, 实现成本更低

相关文章

网友评论

      本文标题:Gson如何解决可变类型集合(Map/List)序列化

      本文链接:https://www.haomeiwen.com/subject/bvhtkrtx.html