Gson如何解決可變類(lèi)型集合(Map/List)序列化

Gson的序列化功能

  • 支持int, long, bool, string等基本類(lèi)型
  • 支持Url, 數(shù)組, atomic, Bean等各種類(lèi)型
  • List, Map類(lèi)型也支持泛型
  • 支持?jǐn)?shù)值Number的轉(zhuǎn)換規(guī)則
  • 支持屬性命名下劃線/駝峰
  • 支持過(guò)濾某寫(xiě)屬性
  • 支持自定義序列化adapter

遇到的問(wèn)題

盡管gson足夠強(qiáng)大, 但是還是遇到了問(wèn)題

譬如:

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);

經(jīng)過(guò)序列化/反序列化,返回的map2, 如下效果:


16433549481788.jpg

可見(jiàn), int, long, float全都轉(zhuǎn)成double; 而bean類(lèi)被轉(zhuǎn)成LinkTreeMap

同理, list也是一樣的問(wèn)題;

解決

可不可通過(guò)自定義gson序列化解決? 答案是可以的

通過(guò)自定義Gson gson = new GsonBuilder(), 然后設(shè)置
.registerTypeHierarchyAdapter(Map.class, new TypeAdapter<Map<String, Object>>()

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

基本原理

定制gson的typeAdapter, 寫(xiě)入時(shí)在數(shù)據(jù)頭部插入map/list的自身和item的class字典信息, 然后解析的時(shí)候解析字典, 反序列化正確的類(lèi)型.

代碼如下

/**
 * Created by Supylc on 2022/1/25.
 * 自定義gson,功能為:
 * 1、能解析可變類(lèi)型的Map, 例如Map<String, Object>
 * 2、能正確解析Map里面的float,double,long,int,short,byte,char等
 * 3、需要用GsonCasual類(lèi)定制的Gson才能正確解析,如果在本類(lèi)寫(xiě)入map,又用外部gson讀,則維持原解析效果
 *
 * 適用場(chǎng)景:
 * 需要用到map或list序列化的地方(特別是item為可變類(lèi)型)
 *
 * 注意:
 * 用GsonCasual寫(xiě)和讀,不要出現(xiàn)用GsonCasual寫(xiě)然后用其他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<>();

    /**
     * 設(shè)置支持map和list的準(zhǔn)確解析,
     * 原理:
     * 1、自定義讀寫(xiě)map和list
     * 2、在寫(xiě)json的頭部,插入class字典,包含Map(或List)和key-value(或list-item)的類(lèi)型信息
     *
     * 字典格式為:
     * 數(shù)組大小,map(list)的類(lèi)型class,value(map或list的item)的類(lèi)型class
     * 如:5,java.util.HashMap, java.lang.long, java.lang.Integer, java.lang.String
     *
     * 把字典信息獨(dú)立放在頭部,對(duì)舊的map數(shù)據(jù)解析不做修改,容錯(cuò)性更好(比如不知情的情況下,用不同的gson進(jìn)行讀寫(xiě),也不報(bào)錯(cuò))
     */
    private static final Gson gson = new GsonBuilder()
            //處理所有的map類(lèi)型
            .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;
                    //在頭部寫(xiě)一個(gè)class字典數(shù)據(jù)
                    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;
                }
            })
            //處理所有的集合類(lèi)型
            .registerTypeHierarchyAdapter(Collection.class, new TypeAdapter<Collection<?>>() {

                @Override
                public void write(JsonWriter out, Collection<?> value) throws IOException {
                    if (value == null) {
                        out.nullValue();
                        return;
                    }
                    out.beginArray();
                    //在頭部寫(xiě)一個(gè)class字典數(shù)據(jù)
                    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類(lèi)型以及item類(lèi)型
        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 即可, 使用也很簡(jiǎn)單

總結(jié)

  1. 原想用parcel方式解決, 因?yàn)閜arcel序列化更直接,更節(jié)省空間
    用記錄class字典的方式, parcel也能實(shí)現(xiàn), 但最后寫(xiě)出來(lái), 其實(shí)也是走gson走過(guò)的路, 因此何必重復(fù)造輪子? 明顯的, parcel不適合用在此場(chǎng)景

  2. 選擇gson的自定義實(shí)現(xiàn), 效率也很高, 實(shí)現(xiàn)成本更低

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容