该文章翻译自Gson Tutorial Series系列教程。该篇主要阐述了如何使用Gson映射Arrays和List集合对象。
Arrays和Lists之间的不同
在进入正题之前,我们想阐述一下Arrays和Lists这两种Java数据结构。他们的Java实现是不同的并且各有各的优势。在你的用例中采取哪种方式取决于软件需求以及你个人的喜好。有趣的是,什么是选择list还是array结构映射到JSON是无关紧要的。
在JSON的数据格式中,没有lists和arrays。是的,Java的实现造成了二者之间巨大的区别,但在就高层次来说,他们都是代表相同的列表结构。在接下来的博客中,我们将他们成为对象列表,但是在Java中他们又是不同的。如果你对此感到迷惑,不用担心,一些例子将会使你更加清晰。
Arrays或者Lists数据的序列化
还记得前一篇博客中关于嵌套对象的restaurant模型吗?现在是时候为它增加一个菜单了。我们想要知道美味食物的价格。一个饭店的菜单可以看成是菜单项的列表结构。每一项都是可供顾客点单的。在我们的简易饭店中,每一项都包括描述和价格。
提示:我们没有考虑菜品分类,组合套餐以及配菜,这使我们的例子足够简单。这很明显不是一个完美的模型,因此不要在你的商业饭店App中使用它……
如果我们考虑怎样具体实现其Java模型,我们将得到接近如下的代码:
public class RestaurantWithMenu {
String name;
List menu;
//RestaurantMenuItem[] menu; // alternative, either one is fine
}
public class RestaurantMenuItem {
String description;
float price;
}
Java处理嵌套对象的方式和JSON是不同的。Java可以将之分离到不同的类中,并由List或者Array的来持有其引用。JSON需要保持一个本地的、嵌套的列表。这意味着在高层次,我们希望JSON像下面这样:
{
"name": "Future Studio Steak House",
"menu": [
...
]
}
就像嵌套对象一样,menu并不拥有一个直接的值。相反,JSON为其值定义了一个由[]包裹着的对象列表。如上面提到的,这是array还是list是没有区别的。在JSON的数据结构中它看起来是相同的。
menu由很多对象组成。在我们的例子中,它们是饭店菜单项。让我们运行Gson查看一个完整的JSON会是什么样子。
我们希望你现在已经知道常规步骤了。得到你的Java对象,初始化Gson然后让Gson创建相应的JSON:
List menu = new ArrayList<>();
menu.add(new RestaurantMenuItem("Spaghetti", 7.99f));
menu.add(new RestaurantMenuItem("Steak", 12.99f));
menu.add(new RestaurantMenuItem("Salad", 5.99f));
RestaurantWithMenu restaurant =
new RestaurantWithMenu("Future Studio Steak House", menu);
Gson gson = new Gson();
String restaurantJson = gson.toJson(restaurant);
restaurantJson包含如下内容:
{
"menu": [
{
"description": "Spaghetti",
"price": 7.99
},
{
"description": "Steak",
"price": 12.99
},
{
"description": "Salad",
"price": 5.99
}
],
"name": "Future Studio Steak House"
}
就跟通常一样,排序有点神奇,列表排在前面是因为按字母来说menu在name的前面。除了排序意外,其他一切都是我们希望的。列表(由[]包裹)包含多个对象(每一个对象由{}包裹)。
然而,我们并不总是发送一个嵌套了列表数据的单独对象,就像上面做的那样。有时候我们也希望发送一个列表。当然,Gson同样支持JSON列表的序列化。例如,如果我们希望序列化如下菜单项列表:
List menu = new ArrayList<>();
menu.add(new RestaurantMenuItem("Spaghetti", 7.99f));
menu.add(new RestaurantMenuItem("Steak", 12.99f));
menu.add(new RestaurantMenuItem("Salad", 5.99f));
Gson gson = new Gson();
String menuJson = gson.toJson(menu);
将会得到如下结果:
[
{
"description": "Spaghetti",
"price": 7.99
},
{
"description": "Steak",
"price": 12.99
},
{
"description": "Salad",
"price": 5.99
}
]
让我们指出重要的不同之处:该JSON的首个字符为[,这就提示了接下来是对象列表!到目前为止,我们只看到由{开始的对象。你应该马上记住它们之间的差异。你将总是需要它并在下一章节马上回用到,如果你继续阅读的话。
Arrays或者Lists的序列化和反序列化
在第二部分我们学习反序列化。换句话说,我们如何使用Gson映射JSON结构中的列表到Java对象。在之前的例子中,我们看到了JSON数据中作为根列表和嵌套在对象中的列表的重要不同。
列表作为根对象
让我们做一个练习。考虑这样一种情况,在未来的环境中我们将会开启自己的API,并会提供一个端口GET /founders。该端口将返回三个对象的数组,每个对象包含一个name域和一个flowerCount域。该数组代表我们桌子上的植物。因此如下JSON列表:
[
{
"name": "Christian",
"flowerCount": 1
},
{
"name": "Marcus",
"flowerCount": 3
},
{
"name": "Norman",
"flowerCount": 2
}
]
是的,你是对的。这个想象的端口直接返回了一个列表。JSON以[]开始和结束。没错,Marcus也喜欢在绿植环绕的办公桌上工作。
因此,我们如何使用Gson将此映射到Java对象呢?第一步是创建模型:
public class Founder {
String name;
int flowerCount;
}
第二部取决于你。你是希望使用Lists还是Arrays作为你的类型呢?
Arrays
如果你想要使用Arrays,那太简单了。跟之前一样直接调用fromJson()方法并且传递数组模型的class,就像:gson.fromJson(founderGson, Founder[].class);
String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";
Gson gson = new Gson();
Founder[] founderArray = gson.fromJson(founderJson, Founder[].class);
这将会产生一个founder的Java数组对象,它们的属性都映射正确:
Lists
鉴于Lists可以扩展容量,因此现在的开发者更喜欢使用Java Lists。不幸的是,你不能直接传递List
Type founderListType = new TypeToken>(){}.getType();
你可以使用该语句的结果作为type供Gson调用:
String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";
Gson gson = new Gson();
Type founderListType = new TypeToken>(){}.getType();
List founderList = gson.fromJson(founderJson, founderListType);
结果跟使用Array得到的结果差不多:
最后,映射你的数据到Array还是List取决于你的个人偏好和用例情况。让我们探讨另一个主题:怎样反序列化一个嵌套在一个对象中的列表:
列表作为一个对象的一部分
我们已经扩展了我们想象的未来环境中的API,端口为GET /info。它返回了比founder更多的信息:
{
"name": "Future Studio Dev Team",
"website": "https://futurestud.io",
"founders": [
{
"name": "Christian",
"flowerCount": 1
},
{
"name": "Marcus",
"flowerCount": 3
},
{
"name": "Norman",
"flowerCount": 2
}
]
}
我们希望你已经熟悉流程了。首先我们需要写一个与JSON响应匹配的模型。我们可以复用之前的Founder类:
public class GeneralInfo {
String name;
String website;
List founders;
}
处理嵌套在一个对象中的列表是简单的,因为Gson不用使用TypeToken就可以简单地处理了。我们可以直接传入class:
String generalInfoJson = "{'name': 'Future Studio Dev Team', 'website': 'https://futurestud.io', 'founders': [{'name': 'Christian', 'flowerCount': 1 }, {'name': 'Marcus','flowerCount': 3 }, {'name': 'Norman','flowerCount': 2 }]}";
Gson gson = new Gson();
GeneralInfo generalInfoObject = gson.fromJson(generalInfoJson, GeneralInfo.class);
产生的结果是完美的:
当然,你也可以使用Founder[]数组代替List
注意:你注意到没有,GeneralInfo和Founder模型拥有相同的name属性,但是Gson却没有出现问题?在序列化和反序列化过程中不会出现任何问题。这真是太神奇了。
嵌套在列表里面的列表
如果你正好奇,在处理嵌套在列表里面的列表时会不会有问题。例如,下面的模型将不会有问题:
public class GeneralInfo {
String name;
String website;
List founders;
}
FounderWithPets类是由宠物列表扩展而来的:
public class FounderWithPets {
String name;
int flowerCount;
List pets;
}
Pet类又拥有玩具的列表
public class Pet {
String name;
List toys;
}
Toy类又包含……好了,循环停止吧。我们希望这个推论可以带给你这样一个观点:你可以在列表中逐层的包含列表而不会有任何问题。Gson就像个冠军一样良好的处理序列化和反序列化。
不过,Gson只能处理包含一致性的对象的列表。如果对象是完全不同的,Gson就不能映射了。尽管可以由多种类型组成列表。