转:
Gson 依赖
本指南将要着手,首先在一分钟内完成一些序列化的准备工作。
由于大多数读者都是Android开发者,我们会为你量身定制,但是Gson也能被用在任何Java环境中。在我们开始之前,我们需要将Gson库拖到我们的项目工程中。截止到写作时间,最新的版本是2.6.2
。如果你正在使用Gradle,添加下面的代码:
compile 'com.google.code.gson:gson:2.6.2'
如果你正在使用Maven,你可以添加下面的依赖:
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
对于那些都没有使用依赖环境系统的可怜家伙,你可以从官方的Github仓库里下载jar包。
Java-JSON序列化基础
让我们做一些系列化!在Gson中序列化是指映射一个java对象到它的JSON表达。在后续的教程中,我们的数据将会变得更复杂,但是我们现在只要从一些非常简单的UserSimple
开始:
public class UserSimple {
String name;
String email;
int age;
boolean isDeveloper;
}
user对象有四个属性:
- user的
name
是一个 String 对象 - user的
email
是一个 String 对象 - user的
age
是一个 integer, 表明是按年来表示的(例如26
,并非准确的生日!) - 一个 boolean 标签
isDeveloper
我们的Android或java应用需要转换一个UserSimple对象到它的JSON表示。假设我们保持成员变量名字一致,我们会为Norman(这篇文章的作者)准备这样的JSON表示:
{
"name": "Norman",
"email": "norman@futurestud.io",
"age": 26,
"isDeveloper": true
}
让我们研究怎么用Gson完成转换。首先,我们需要为Norman创建一个Java对象。
UserSimple userObject = new UserSimple(
"Norman",
"norman@futurestud.io",
26,
true
);
译者注:如果没有在UserSimple类里添加对应的构造函数,上面会报错。添加也很简单,UserSimple类内部编辑界面,右键—>Generate->Constructor->全部选中 ,点击ok。自动生成如下代码:
public UserSimple(String name, String email, int age, boolean isDeveloper) {
this.name = name;
this.email = email;
this.age = age;
this.isDeveloper = isDeveloper;
}
为了完成序列化,我们需要一个Gson
对象来操作转换。我们可以简单的使用下面的创建:
Gson gson = new Gson();
为了开始序列化,我们需要调用toJson()
方法,然后传递UserSimple
对象:
String userJson = gson.toJson(userObject);
userJson
对象包含了下面的值:
{
"age": 26,
"email": "norman@futurestud.io",
"isDeveloper": true,
"name": "Norman"
}
Gson改变了属性的顺序(按字母顺序),但是内容是一样的!注意Gson是如何表示这些类型的。String值用“”
包裹,但integer值却没有包裹。我们不需要在JSON对象或复制单个成员上浪费时间。一个Gson的简单调用足以映射整个对象。当我们处理非常复杂的数据结构时,这是非常方便的。但在我们进一步深入之前,我们测试另一个方向。Gson可以从上面的JSON数据中创建一个java对象么?
java-JSON 反序列化基础
首先,我们需要创建一个字符串,包含上面提到的JSON:
String userJson = "{'age':26,'email':'norman@futurestud.io','isDeveloper':true,'name':'Norman'}";
我们将 "
变为 '
,是为了避免大量的\"
转义。它就是这样工作的。下一步,可能你已经猜到了,创建一个Gson实例:
Gson gson = new Gson();
最后,我们必须用fromJson()
映射一个JSON到一个Java对象:
UserSimple userObject = gson.fromJson(userJson, UserSimple.class);
注意我们是怎样将Java对象作为第二个参数进行传递。否则Gson不知道应该将JSON映射。它不是一个魔术师!
如果我们添加了一个debugger并且检查了user对象的结果,它会展示Gson成功地准确映射了所有属性:
出现Null值,会发生什么?
我们假设你已经看了第一篇Gson的文章,它介绍了一个UserSimple
类和它的成员变量。在之前的例子中,所有的值都是设定好的。如果他们是空值(null
)呢?例如,我们创建了一个用户,他有邮箱、年龄并且是一个开发者,但是名字是null
?
UserSimple userObject = new UserSimple(null, "norman@futurestud.io", 26, true);
这是有效的Java代码,会创建一个普通的UserSimple
对象。如果我们现在让Gson去创建匹配的JSON,将会是什么样子的呢?
UserSimple userObject = new UserSimple(null, "norman@futurestud.io", 26, true);
Gson gson = new Gson();
String userJson = gson.toJson(userObject); // userJson = ??
Gson将会创建下面的JSON数据:
{
"age": 26,
"email": "norman@futurestud.io",
"isDeveloper": true
}
在序列化中,Gson简单地忽略了null
值。如果某个值没有设置,它根本不会成为解析出的JSON中的一部分。如果你需要那个null
值的字段也在JSON中,Gson有这么一个选项可以设置。但要到后面才讲到。
现在明确下很重要的一点:在序列化过程中Gson不会为null
值创建任何JSON数据。
反序列化过程也非常相似。假设我们有下面的JSON数据,但缺少了邮箱(email):
{
"age": 26,
"isDeveloper": true,
"name": "Norman"
}
即使邮箱(email)缺失了,Gson仍然会尽力尝试映射。它会为那些不存在的字段设置为null
:
当然,设置一个字段为null
非常简单,但是如果一个字段并不是像String一样可以设置null
呢?例如,想像一下下面的JSON:
{
"email": "norman@futurestud.io",
"name": "Norman"
}
age
(int
类型)和isDeveloper
(boolean
类型)并不接受null
值。在这个例子中,Gson任然不会报错,会设置它们默认的值(分别设置0
和false
):
嵌套对象的序列化
我们喜欢用实际的例子来演示功能,所以,让我们扩展UserSimple模型。在之前的文章中,user模型只有一些标准的java类型:
public class UserSimple {
String name;
String email;
boolean isDeveloper;
int age;
}
现在我们的user也有了一个家庭地址,这个家庭地址也有自己的模型类UserAddress
:
public class UserNested {
String name;
String email;
boolean isDeveloper;
int age;
// new, see below!
UserAddress userAddress;
}
public class UserAddress {
String street;
String houseNumber;
String city;
String country;
}
换句话说,user现在是用一个UserNested
模型表示,其中有一个额外的一对一的地址对象。地址是在UserAddress
模型中表示的。
在java中,这两个模型能够通过类清晰地区分,并且我们通过UserAddress userAddress
字段保持引用。然而,在JSON里,没有类或引用。只有嵌套(nest)用户地址到用户对象中。基本地,在JSON里我们只要用{}
在字段名字后创建一个新的对象:
{
"age": 26,
"email": "norman@futurestud.io",
"isDeveloper": true,
"name": "Norman",
"userAddress": {
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
}
不同于其他属性(age
,email
,...),新的userAddress
没有直接的值。而是有一些包含在{}
内的子值。很重要的一点,需要你理解字段名字后面有括号的就是表明这是一个嵌套对象。
太多的理论。是时候见识一下Gson从UserNested
对象中创建了什么。你可能认识这个模式,Gson不需要任何配置。它会自动根据传进的类推断出数据结构:
UserAddress userAddress = new UserAddress(
"Main Street",
"42A",
"Magdeburg",
"Germany"
);
UserNested userObject = new UserNested(
"Norman",
"norman@futurestud.io",
26,
true,
userAddress
);
Gson gson = new Gson();
String userWithAddressJson = gson.toJson(userObject);
你应该会很好奇,字符串userWithAddressJson
的值是什么样子的:
{
"age": 26,
"email": "norman@futurestud.io",
"isDeveloper": true,
"name": "Norman",
"userAddress": {
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
}
细心的你很容易发现,虽然Gson又一次按照字母顺序存储了字段,但结果正是我们期望的。Gson正确地创建了嵌套userAddress
JSON对象。当然,我们可以为用户的薪水方法或工作地址增加更多的嵌套对象。甚至嵌套对象里也能有嵌套对象!
在下一节中,我们会研究另一个方向。如何反序列化复杂的嵌套JSON到Java对象呢?
嵌套对象的反序列化
上一节中,我们假设模型已经有了,并且我们只要创建一个匹配的JSON。特别是对于在真实世界里的开发者,经常是相反的。API接口正在返回一些JSON数据,我们需要为那些数据创建模型类。
如果你已经读了第一篇文章的一些内容,你已经感觉到如何创建一个模型类。防止你觉得无聊,所以我们将从user的例子转移到一个精致的小餐馆。
{
"name": "Future Studio Steak House",
"owner": {
"name": "Christian",
"address": {
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
},
"cook": {
"age": 18,
"name": "Marcus",
"salary": 1500
},
"waiter": {
"age": 18,
"name": "Norman",
"salary": 1000
}
}
这来自于我们的API接口,并且我们要利用Gson去自动创建匹配的Java对象。首先,你需要模型化基础类,所有顶级字段都在里面:
public class Restaurant {
String name;
Owner owner;
Cook cook;
Waiter waiter;
}
看一下我们是如何为name
创建一个String字符串,以及另外三个扩展的Java类?有些人自已实现了代码,可能与下面的结果不同。事实上,创建一个Java对象并不需要太明确。例如,基于JSON,我们看到了有一样结构的cook
和watier
嵌套对象。你可以仍然创建一个不同的类,像上面做的一样,或者为他们俩只创建一个普通的Staff
类。
public class Restaurant {
String name;
Owner owner;
Staff cook;
Staff waiter;
}
其实,两个方法任意一个都可行。如果你还不确定用哪个,我们通常会创建一个额外的类避免未来可能发生的冲突。例如,如果cook模型改变了,但是waiter模型保持不变,你可能需要改变一堆代码。这样,我们暂时抛弃Staff
的解决方案。当然,我们仍要为第二层次的对象创建Java模型类:
public class Owner {
String name;
UserAddress address;
}
public class Cook {
String name;
int age;
int salary;
}
public class Waiter {
String name;
int age;
int salary;
}
好的,因为这个UserAddress
类到哪都能适用,我们作了一点弊,第一部分重复使用了UserAddress
。 ;-)
总之,我们希望你理解从一个JSON字符串创建Java模型类的过程。你需要从高层到最深层次遍历,直到你的嵌套JSON只剩下常规类型。
既然主要工作已经完成了,我们可以把一切交给Gson。当然,如果我们之前所做的工作都是正确的,它会优雅地处理一切,并创建只有几行的Java对象:
String restaurantJson = "{ 'name':'Future Studio Steak House', 'owner':{ 'name':'Christian', 'address':{ 'city':'Magdeburg', 'country':'Germany', 'houseNumber':'42', 'street':'Main Street'}},'cook':{ 'age':18, 'name': 'Marcus', 'salary': 1500 }, 'waiter':{ 'age':18, 'name': 'Norman', 'salary': 1000}}";
Gson gson = new Gson();
Restaurant restaurantObject = gson.fromJson(restaurantJson, Restaurant.class);
restaurantObject
对象自动地包含了JSON里的所有的信息 :
提示:从JSON创建Java模型类是一项单调乏味的工作。在你掌握了概念后,你可能想要使用工具自动化处理。我们相当喜欢用jsonschema2pojo.org。
Java中Map的序列化
Java的Map是一个非常灵活的数据类型,可以被用在各种各样的场景中。它允许我们开发者用Java语言去物化许多真实世界的情况。由于Java的Map使用非常广泛,我们可能不会覆盖到你正在用的案例,但将会覆盖到所有的方法。
让我们用一个场景来开始,你的应用有员工姓名的列表,你被要求去实现一个以某个特定字母开头的所有员工展示界面。例如,用户可以选择字母A
,此时,你的应用会返回三个匹配的员工名字:Andreas
、Aden
和Arnold
。第一次迭代只是列出了所有名字的列表,但是性能往往是不够的。因此,转去使用HashMap
实现,第一个字母作为键(Key)(如:A
),值将是一个名字的列表。
如果我们创建我们的HashMap
,我们的Java代码将会是这样:
HashMap<String, List<String>> employees = new HashMap<>();
employees.put("A", Arrays.asList("Andreas", "Arnold", "Aden"));
employees.put("C", Arrays.asList("Christian", "Carter"));
employees.put("M", Arrays.asList("Marcus", "Mary"));
Map的序列化与其他类型是一样的,你可以将其直接传给Gson,Gson会正确地处理:
Gson gson = new Gson();
String employeeJson = gson.toJson(employees);
返回的JSON数据:
{
"M": [
"Marcus",
"Mary"
],
"C": [
"Christian",
"Carter"
],
"A": [
"Andreas",
"Arnold",
"Aden"
]
}
每个键(A
,C
和M
)都有自己的名字列表,这正是我们想要的。
Java中Map的反序列化
如果你看了前面一节的JSON数据,或者下面的JSON数据。你会问你自己:如何才能区分一个集合和多个对象呢?答案很简单:你不能。这是为数不多的几个特性之一,JSON数据表示是模棱两可的。看看下面的列子:
{
"1$": {
"amount": 1,
"currency": "Dollar"
},
"2$": {
"amount": 2,
"currency": "Dollar"
},
"3€": {
"amount": 3,
"currency": "Euro"
}
}
在JSON中,读者可以假设有名字分别是1$
、2$
和3€
的3个对象,每个对象都有自己的值。但另一方面也能被看作是一个简单的Map,1$
、2$
和3€
是键(Key)。
没有可靠的方法评估一个JSON数据类型是什么。这里提供一些关键点来帮助你:
- 第一且最重要的:上下文知识!如果你有文档或已经知道所描述的对象应该是怎样的,你应该能够区分单独的对象和Map数据。
- 值的数据类型是一致的吗?一致的就可能是Map
- 对象名称/键是动态的和广泛的吗?这也是Map类型的提示。
我们可以使用在列表对象文章中展示的TypeToken
方法。你用我们期望的数据类型通过创建一个新的TypeToken
得到一个Type方法:
public class AmountWithCurrency {
String currency;
int amount;
}
String dollarJson = "{ '1$': { 'amount': 1, 'currency': 'Dollar'}, '2$': { 'amount': 2, 'currency': 'Dollar'}, '3€': { 'amount': 3, 'currency': 'Euro'} }";
Gson gson = new Gson();
Type amountCurrencyType =
new TypeToken<HashMap<String, AmountWithCurrency>>(){}.getType();
HashMap<String, AmountWithCurrency> amountCurrency =
gson.fromJson(dollarJson, amountCurrencyType);
amountCurrency
变量实际上持有了整个正确的键、值集合:
嵌套的Map
Map数据结构不会成为模型/JSON的根元素。它只能是一个对象的属性。你得用像处理列表一样的方式序列化和反序列化它。我们也在这里发布了为嵌套行为的指导方式。
原文链接:http://www.jianshu.com/p/c75d67371f84
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。