Gson,不规范json的反序列化

  Gson是Java或Android开发中常用的一个json解析库,尤其是在Android下基本上是必备的。但是做项目嘛,总会遇到各种奇葩的问题,这里所说的不规范json并不是说json格式不规范,毕竟格式都不规范的话就谈不上是个json串了,是不可能解析的。这里所谓的不规范json是在后台反回的json串能解析的情况下,我们的实体类不能很方便的接收它,看下面的情况(由此可见后台数据格式对前端的影响之大,有时候对后台友好的数据格式对前端来说并不友好反而会增加前端的工作量,尤其是像Java这种强类型语言来说,要想用的方便那就每一个字段都要严格限制它的数据类型):

[
    {
        "id": 1097320752316833800,
        "newsInfo": {
            "geolocation": {
                "lon": "0",
                "lat": "0"
            },
            "type": "5",
            "id": "1097320752316833794",
            "contentId": 1097320752316833800,
            "publishTime": "2019-02-18 10:23:49"
        },
        "freshnewsInfo": "",
        "activityContent": "",
        "specialInfo": "",
        "eventInfo": "",
        "contentType": "1"
    },
    {
        "id": 1097320752316833800,
        "newsInfo": "",
        "freshnewsInfo": {
            "sourceType": 2,
            "videoImgUrl": "",
            "name": "用户TrrLXe",
            "relatednewsId": "",
            "fileId": "",
            "imeiNo": "",
            "createTime": "2019-02-18 09:44:04",
            "geolocation": {
                "lon": 116.4835,
                "lat": 39.9235
            }
        },
        "activityContent": "",
        "specialInfo": "",
        "eventInfo": "",
        "contentType": "1",
        "sequence": "",
        "createTime": "2019-02-18 10:56:25",
        "showType": 1
    }
]
public class Content{
    private long id;
    private NewsInfoBean newsInfo;
    private FreshnewsInfoBean freshnewsInfo;
    private String activityContent;
    private EventInfoBean eventInfo;
    private SpecialInfoBean specialInfo;
    private int contentType;
    private String sequence;
    private String createTime;
    private String showType;
}

  上面的json串相当于一个List,每个Content中都有若干个*Info(newsInfo、SpecialInfo、eventInfo等)字段,其中只有一个*Info字段有值,其余没有值的理论上应该为null,但是后台返回的是空字符串,这样问题就大了,当为空字符串的时候怎么解析呢?空字符串是无法赋到一个实体对象的,对于Gson来说会报下面这样一个错误:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 26 path $.data[0].newsInfo

  意思是说要解析的newsInfo值应该是个Object类型,但现在却是一个String类型,所以解析失败了。好吧,这个问题跟后台人员反应让他们把空字符串转为null,但他们也很无奈,反复沟通后无果,最终还是把问题抛给了前端/(ㄒoㄒ)/~~。
  其实这个问题还是有不少解决方式的,比如改下实体结构:

public class Content{
    private long id;
    private Object newsInfo;
    private Object freshnewsInfo;
    private String activityContent;
    private Object eventInfo;
    private Object specialInfo;
    private int contentType;
    private String sequence;
    private String createTime;
    private String showType;
}

  将所有不能正常解析的字段都改用Object类型接收,然后再根据需要解析Object到具体的实体结构;或者是使用Gson框架中的JsonParse类解析返回的json数据,这两种方式都可以,但对于整个项目来说,有不少接口是这种不规范的格式,每个都这样做的话无疑增加了成本和复杂度,此外还有一个更令人抓狂的问题,而且我相信很多人都碰到过。
  还是上面的json串,在newsInfo和refreshInfo字段中,有个geolocation字段:

"newsInfo": {
    "geolocation": {
        "lon": "0",
        "lat": "0"
    },
    "type": "5",
    "id": "1097320752316833794",
    "contentId": 1097320752316833800,
    "publishTime": "2019-02-18 10:23:49"
}

  对于后台来说,从不同的表中查出的字段放到一个单独的对象实体中没问题,但对于前端来说,放到和其他内容一个级别下才是最方便的,比如我们期望它的解析实体是长这样的:

public class NewsInfo {
    private double lon;
    private double lat;
    private int type;
    private long id;
    private long contentId;
    private String publishTime;
}

  如果这个json串中还有地址信息、作者信息话那有可能会是这样的:

"newsInfo": {
    "geolocation": {
        "lon": "0",
        "lat": "0"
    },
    "type": "5",
    "id": "1097320752316833794",
    "contentId": 1097320752316833800,
    "publishTime": "2019-02-18 10:23:49"
    "address": {
        "country":"CN",
        "countryNO":"86",
        "city":"北京"
    },
    "author": {
        "nickName":"xiaxia",
        "icon":"",
        "registTime":"2018-12-18 09:33:50",
        "type":"1",
    }
}

  而我需要的只是address中的city或是author中的nickName和icon字段,所以我们期望它的解析实体是这样:

public class NewsInfo {
    private double lon;
    private double lat;
    private String city;
    private String nickName;
    private String icon;
    private int type;
    private long id;
    private long contentId;
    private String publishTime;
}

  这种实体结构,使用Gson是无法正常解析上面的json串的,但Gson框架提供了强大的可扩展功能,我们完全可以自定义解析方式来达到我们的需求。

Gson序列化和反序列化的方式

  自定义json解析,Gson框架为我们提供了以下几种方式:

  • 继承TypeAdapter类

  需要重写read(JsonReader in)反序列化方法和write(JsonWriter out, Object value)序列化方法,扩展性低,只能针对一种类型进行处理。

  • 实现TypeAdapterFactory接口

  需要重写create(Gson gson, TypeToken<T> type)方法,并返回一个TypeAdapter对象,扩展性高,可通过判断类型来创建对应的TypeAdapter

  • 实现JsonDeserializer或JsonSerializer接口

  扩展性高,将序列化和反序列化分开,可只针对其中一种进行自定义

  这3种方法本质上都是创建一个新的TypeAdapter,其中前两种需要重写序列化和反序列化方法,第三种是将序列化和反序列化方法分开了,可以单独实现一种,我们这里使用第三种方式,实现JsonDeserializer接口,这样的话我们就只自定义了反序列化方法,序列化方法仍然走Gson框架本身。

Gson反序列化过程

  在Gson框架中,反序列化的过程是这样的:
1. 解析json串后得到一个JsonReader;
2. 通过hasNext()迭代JsonReader获取当前json串的位置;
3. 再通过peek()方法判断该位置下的类型(BEGIN_OBJECT、BEGIN_ARRAY、END_ARRAY、END_OBJECT、NAME等,具体可看JsonToken类);
4. 如果该类型是个NAME就说明遇到一个字段,那么通过nextName()方法获取该字段的值,然后执行第5步骤,否则继续执行第3步骤;
5. 如果实体对象中有该字段,那再获取该字段的数据类型;
6. 通过判断该数据类型来决定接下将会通过JsonReader的哪个方法(nextString()、beginObject()、beginArray)获取到字段的值。

  以上就是Gson中反序列化的过程,我们上面发生Gson报错的地方就是在第6步发生的,由于newsInfo字段是个Object,但在JsonReader中却是一个String,所以对于一个String类型来说如果执行beginObject()方法就会报错。这里也是我不明白的地方,对于Gson框架来说为什么不通过peek()方法先判断一下类型进行容错然后再获取值呢。

自定义反序列化过程

  Gson的反序列化过程是顺序执行的,从json串的头开始一直迭代到尾遇到什么就获取什么,可谓是行云流水一气呵成。接下来针对我们的需求重新定义一下反序列化过程,主要有三个目标:

1.摆脱后台数据格式对前端的影响,前端实体类不一定完全按照后台的格式写
2.增加json反序列化的容错能力
3.获取json中不同层级的值

1. 实现JsonDeserializer接口,并重写其中方法;
2. 获取实体类的所有字段,并获取字段的注解信息;
3. 获取JsonElement中的members字段,这是一个LinkedTreeMap类型,存储了解析json后的层级结构;
4. 遍历字段,获取members中对应字段的值,该值也是一个JsonElement类型;
5. 判断字段类型和该JsonElement类型是否一致,避免类型不同而在赋值时报错;
6. 如果该类型是基本类型则进行赋值操作,否则该类型可能是数组或实体类,那就重复执行第四步骤;

  很显然,该反序列化过程不是顺序执行的,而是根据json的层次结构进行查找的,虽然速度上落后了但灵活性却提高了,可以根据实体字段随意查找想要的值。

使用方式

@JsonAdapter(value = IllegalJsonDeserializer.class)
public class Content{
    private long id;
    @Select(value = "address.city")
    private String city;
    private NewsInfoBean newsInfo;
    private FreshnewsInfoBean freshnewsInfo;
    private String activityContent;
    private EventInfoBean eventInfo;
    private SpecialInfoBean specialInfo;
    private int contentType;
    private String sequence;
    private String createTime;
    private String showType;
}
  1. 在需要反序列化的类或字段上直接使用@JsonAdapter(value = IllegalJsonDeserializer.class)注解
  2. 如果需要获取其他层级的值,可在字段上声明@Select(value = "address.city"),其中value的值用.作为分隔符作为不同层级的划分,比如上面的值就是说明要将address下的city字段赋值到Content类中的city字段

  除此之外,该反序列方法还兼容了Gson本身的注解,完全可以使用Gson的原生注解方法。源码已放在github上,如果有遇到同样问题或者对此感兴趣朋友的可以看一下。

https://github.com/chengzhicao/illegal-json

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,192评论 2 18
  • 为了更好的学习Gson,特将Gson User Guide翻译如下。由于本人英文水平有限,如有错误,还请指正,谢谢...
    WeberLisper阅读 6,699评论 0 6
  • 概述 Moshi是Square公司在2015年6月开源的有关Json的反序列化及序列化的框架,说到Json,大家应...
    wustor阅读 12,716评论 7 33
  • 好久不记得梦了 今早又做梦 被梦吓醒 喝了口温水睡意全无 不敢奢望更多 只求不再梦见 我不好 对不起不祝你好
    梦梦梦梦happy阅读 146评论 0 0
  • 夜晚下起了大雨: 南宫雨轩被雷声吵醒,这时雨轩想起自己的手链忘在海岸悬崖边,雨轩走进南宫雨夜的房间,雨...
    柠檬哆蜜枣阅读 227评论 0 0