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

Gson的序列化功能

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

遇到的問題

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

譬如:

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)過序列化/反序列化,返回的map2, 如下效果:


16433549481788.jpg

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

同理, list也是一樣的問題;

解決

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

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

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

基本原理

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

代碼如下

/**
 * 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讀单山,則維持原解析效果
 *
 * 適用場(chǎng)景:
 * 需要用到map或list序列化的地方(特別是item為可變類型)
 *
 * 注意:
 * 用GsonCasual寫和讀碍现,不要出現(xiàn)用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<>();

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

                @Override
                public void write(JsonWriter out, Collection<?> value) throws IOException {
                    if (value == null) {
                        out.nullValue();
                        return;
                    }
                    out.beginArray();
                    //在頭部寫一個(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類型以及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 即可, 使用也很簡(jiǎn)單

總結(jié)

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窘俺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子复凳,更是在濱河造成了極大的恐慌瘤泪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育八,死亡現(xiàn)場(chǎng)離奇詭異对途,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)髓棋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門实檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人按声,你說我怎么就攤上這事膳犹。” “怎么了签则?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵须床,是天一觀的道長。 經(jīng)常有香客問我渐裂,道長侨颈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任芯义,我火速辦了婚禮哈垢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扛拨。我一直安慰自己耘分,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著求泰,像睡著了一般央渣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渴频,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天芽丹,我揣著相機(jī)與錄音,去河邊找鬼卜朗。 笑死拔第,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的场钉。 我是一名探鬼主播蚊俺,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逛万!你這毒婦竟也來了泳猬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤宇植,失蹤者是張志新(化名)和其女友劉穎得封,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體指郁,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忙上,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坡氯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洋腮,死狀恐怖箫柳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啥供,我是刑警寧澤悯恍,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站伙狐,受9級(jí)特大地震影響涮毫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贷屎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一罢防、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唉侄,春花似錦咒吐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽候生。三九已至,卻和暖如春绽昼,著一層夾襖步出監(jiān)牢的瞬間唯鸭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工硅确, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留目溉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓疏魏,卻偏偏與公主長得像停做,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子大莫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容