Android中Gson的使用

Android中Gson的使用

1 简介

Gson是一个Java库,作用是将Java对象转换成它对应的JSON表示。
开源的JSON解析库有好几个,例如Jackson就是一个很有名的XML/JSON解析库。Gson与这些库的不同之处,表现在它的两个重要设计目标:

  • 即使你无法修改源代码,你也能通过Gson对代码中的类做解析和变换。
  • 充分支持Java泛型。

在学习过程中我们能发现,第一点很好理解,并且也简化了对Gson的使用;而第二点对泛型的支持,则是掌握Gson框架的一个难点。

下图是Gson的设计目标:


image

其中功能性是难点,也比较不好理解,先重点掌握Gson的基本使用方式即可。

2 快速上手

Gson的基本使用是非常简单的。
首先在Android Studio项目中添加对Gson库的依赖:

compile 'com.google.code.gson:gson:2.6.2'

3 简单回顾Json

在正式开始使用Gson之前,先来简单回顾一下JSON的基础知识。
JSON是一种轻量级的数据交换格式,它主要有两种结构:
键值对的集合,对应于其它编程语言中的对象(例如Java)、struct(例如C)、Hash(例如Ruby)、字典(例如Python)等。
无序的值列表,对应于编程语言中的数据结构:数组、矢量、列表等。
注意JSON使用的这两种数据结构的简洁性,它们是不同编程语言之间通用的,这赋予了JSON连接不同编程语言的能力。
JSON中主要有5种数据形式:

  • number,例如10
  • string,例如“abcd”
  • value,value可以是上面的number和String,也可以是下面的object和array,value的概念赋予了JSON嵌套表示的能力。value还包括三个特殊值:true,false和null。
  • array,形如 [value, value, ... value] 的列表形式。
  • object,形如 {string:value, string:value, ... string:value} 的键值对形式。

4 使用Gson

Gson库的关键类就是Gson,使用起来很简单,直接实例化 new Gson() 即可。它也可以通过GsonBuilder 类来实例化,进行一些初始化设置,初学时无需深入了解。
Gson实例是无状态的。所以多个JSON序列化和反序列操作完全可以重用一个Gson对象。

4.1 基本序列化方法

序列化是指将Java对象转换成JSON字符串,使用Gson.toJson(...)方法来进行序列化操作。举几个基本的序列化例子:

  • number:
Gson gson = new Gson();
gson.toJson(1);            // ==> 1
gson.toJson(new Long(10)); // ==> 10
  • string:
gson.toJson("abcd");       // ==> "abcd"
  • value:
gson.toJson(true);         // ==> true
  • object:
class BagOfPrimitives {
    private int value1 = 1;
    private String value2 = "abc";
    private transient int value3 = 3;
    BagOfPrimitives() {
    // no-args constructor
    }
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);  
// ==> json is {"value1":1,"value2":"abc"}
  • array:
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
// Serialization
gson.toJson(ints);     // ==> [1,2,3,4,5]
gson.toJson(strings);  // ==> ["abc", "def", "ghi"]

4.2 基本反序列化方法

反序列化是指将JSON字符串传换成Java对象,使用Gson.fromJson(...)方法来进行反序列化操作。反序列化和序列化不同的一点是,你需要告诉Gson目标对象的类型是什么。举几个基本的反序列化例子:

  • number:
int one = gson.fromJson("1", int.class);
Long one = gson.fromJson("1", Long.class);
  • string:
String str = gson.fromJson("\"abc\"", String.class);
  • value:
Boolean f = gson.fromJson("false", Boolean.class);
  • object:
class BagOfPrimitives {
    private int value1 = 1;
    private String value2 = "abc";
    private transient int value3 = 3;
    BagOfPrimitives() {
    // no-args constructor
    }
}
String json = "{"value1":1,"value2":"abc"}";  
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
  • array:
int[] ints = {1, 2, 3, 4, 5};
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class); 
// ==> ints2 和 ints 相同。

4.3 序列化和反序列化中的注意点

  • 对象的字段可以并推荐使用private。
  • 无需使用注解指定哪些字段被包括在序列化和反序列化操作中。当前类,和它所有父类的所有字段都是默认包括的。
  • 如果字段使用transient修饰,它被默认忽略,不包含在序列化和反序列化操作中。
  • Gson可以正确处理null值。
  • 序列化时,值为null的字段会被跳过。
  • 反序列化时,JSON中缺失的项,其对象对应的字段会被设为null。
  • 内部类、匿名类等隐含的外部类的字段会被忽略,不包含在序列化和反序列化中。

5 Gson的进阶使用

5.1 修改字段名

从上面的例子可以看出来,在序列化和反序列化时,字段名直接用来作为键值对中的key。有时,对象和它的JSON表示命名并不完全一致,这时就需要修改字段名,有两种方法:
使用@SerializeName注解自定义字段名;
使用FieldNamingPolicy来规定基本的命名策略;
例如:

private class SomeObject {
  @SerializedName("custom_naming") 
  private final String someField;

  private final String someOtherField;

  public SomeObject(String a, String b) {
    this.someField = a;
    this.someOtherField = b;
  }
}

SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);

结果将是

{"custom_naming":"first","SomeOtherField":"second"}

5.2 嵌套类(例如内部类)的情况

对于静态内部类,Gson可以直接处理。
对于非静态内部类,Gson不能自动反序列化,因为即使是非静态内部类的无参构造器,也需要一个外部类的引用,这在反序列化期间是做不到的。要解决这个问题,你要么将内部类声明为静态,要么提供一个自定义的InstanceCreator。例如:

public class A { 
  public String a; 

  class B { 

    public String b; 

    public B() {
    // No args constructor for B
    }
  } 
}

注意:默认情况下,B类不能用Gson序列化。

Gson不能讲{"b":"abc"}反序列化成B的对象,除非将B定义成静态的。另一个解决方法是写一个自定义的InstanceCreator:

public class InstanceCreatorForB implements InstanceCreator<A.B> {
  private final A a;
  public InstanceCreatorForB(A a)  {
    this.a = a;
  }
  public A.B createInstance(Type type) {
    return a.new B();
  }
}

这是可行的,但不推荐这样来用。

5.3 集合的情况

Gson对集合的处理方式比较丑陋,这是由于Java泛型机制决定的,没有更好的解决办法:

Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);

// Serialization
String json = gson.toJson(ints);  // ==> json is [1,2,3,4,5]

// Deserialization
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 和 ints 相同。

如果集合中存储的是任意类型的对象,Gson可以序列化它,但不能做反序列化,这是因为用户无法指明集合中每一个对象的类型。在反序列化时,集合必须是一个明确的类型。如果遵循良好的Java编程规范,这基本不会造成什么问题。

5.4 泛型的处理

泛型的处理和上面集合的处理类似,因为集合定义中使用的就是泛型。
当你调用toJson(obj)方法时,Gson使用obj.getClass()来获取类属性信息,来序列化它。类似的,通常你可以将MyClass.class对象传递给fromJson(json, MyClass.class)方法,来反序列化。如果对象不包含泛型,这种方法是可行的,但是,如果对象是一个泛型类型,由于Java的类型擦除(Type Erasure)机制,该泛型的信息会丢失,例如:

class Foo<T> {
  T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // 可能无法正确序列化foo.value。

gson.fromJson(json, foo.getClass()); // 不会将 foo.value 正确反序列化成Bar对象

要解决这一问题,你需要指明你的泛型类型的类型参数(parameterized type),使用集合的例子中看到过的TypeToken类:

Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);

gson.fromJson(json, fooType);

初步使用Gson时,可以不必深究这一泛型处理的实现原理,知道怎么做即可。

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