Gson — 数组和对象列表的映射

欢迎回到我们 Gson 系列的另一篇文章。 在之前的几篇文章中我们介绍了 Gson 的基础知识、模型注释和嵌套对象的映射,现在我们将继续讨论一个核心特性:数组和列表的映射。 几乎每个数据模型都使用某种形式的列表。 幸运的是,Gson 可以很容易地处理它们。


数组和列表之间的区别

在我们进入特定的(反)序列化示例之前,我们要检查两个 Java 结构 Arrays 和 Lists。 Java 实现有很大的不同,任何一种都有其优点。 我们将在用例中应用什么取决于软件要求,至少部分取决于我们自己的个人品味。 将列表或数组结构映射到 JSON 时,有趣的是:这与他们是数组还是列表并没有关系。

在 JSON 数据格式中,没有列表或数组。 是的,Java 实现在它们之间产生了巨大的差异,但在高层次上,它们以列表形式表示完全相同的数据。 在博客文章的其余部分,我们将它们命名为对象列表,但在 Java 端它们可以是任何一种。 如果这有点令人困惑,请不要担心,在几个例子之后会变的清楚。


数组/列表的数据序列化

还记得上一篇关于嵌套对象的博客文章中的模型吗? 是时候给餐厅添加菜单了,不是吗? 我们想知道以什么价格提供什么美味的食物。 餐厅菜单可以构造为菜单项列表。 一件商品正是客户可以订购的一种选择。 在我们简单的 restaurant 中,每件商品都有 description 和 price 。

提示:我们将尽量保持结构简单,不处理菜单类别、组合或配菜。 这显然不是一个完整的模型,所以不要将它用于实际的商业餐厅应用程序……

如果我们考虑具体的 Java 模型实现,我们会得到类似于下面的代码:

public class RestaurantWithMenu {  
    String name;

    List<RestaurantMenuItem> menu;
    //RestaurantMenuItem[] menu; // 另一种选择,任何一个都可以
}

public class RestaurantMenuItem {  
    String description;
    float price;
}

只是对于嵌套对象,Java 处理对象的方式与 JSON 不同。 Java 可以将它们保留为单独的类,并且只保留对 List 或 Array 实现的引用。 JSON 需要将列表保存为本地嵌套列表。 这意味着在高层次上,我们期望在 JSON 方面是这样的:

{
  "name": "迹忆客美食馆",
  "menu": [
    ...
  ]
}

与嵌套对象类似,我们没有直接的 menu 值。 相反,JSON 通过用 [] 包装值来声明即将到来的对象列表。 如上所述,这是数组还是列表没有区别。 在 JSON 数据结构中,它看起来是一样的。

menu 的内容是一堆对象。 在我们的例子中,它们是餐厅的菜单项。 让我们运行 Gson 来看看完整的 JSON 是什么样子。

我们希望大家现在已经知道这个练习了。 获取 Java 对象,初始化 Gson,然后让 Gson 创建匹配的 JSON:

List<RestaurantMenuItem> 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("迹忆客美食馆", menu);

Gson gson = new Gson();  
String restaurantJson = gson.toJson(restaurant);  

restaurantJson 包含以下内容:

{
  "name": "迹忆客美食馆",
  "menu": [
    {
      "description": "Spaghetti",
      "price": 7.99
    },
    {
      "description": "Steak",
      "price": 12.99
    },
    {
      "description": "Salad",
      "price": 5.99
    }
  ]
}

该列表(由 […] 表示)包含多个对象(每个由 {…} 表示)。

但是我们并不总是发送嵌套在单个对象中的数据列表,就像我们在上面所做的那样。 有时我们只想发送一个列表。 当然,Gson 也支持列表的 JSON 序列化。 例如,如果我们只是像这样序列化菜单项:

List<RestaurantMenuItem> 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 的第一个字符是 [,表示即将到来的对象列表! 到目前为止,我们只查看了以 { 开头的对象。 我们应该立即尝试记住这种差异。 如果大家继续阅读,那么我们将在下一节中一直需要它。


数组/列表的数据反序列化

在第二部分中,我们将完成反序列化。 换句话说,我们如何使用 Gson 从 JSON 结构中的列表映射到 Java 对象。 在前面的示例中,我们已经研究了列表是根还是嵌套在 JSON 数据中的对象中的重要区别。

根对象列表

所以让我们做一个练习。 如果 迹忆客 将启动我们自己的 API,我们还将提供一个端点 GET /founders。 这个端点将返回餐厅里的三个人的名字和一个flowerCount,它显示了我们桌子上的植物数量。 那么下面的 JSON 是否有一个作为 root 的列表?

[
    {
      "name": "Christian",
      "flowerCount": 1
    },
    {
      "name": "Marcus",
      "flowerCount": 3
    },
    {
      "name": "Norman",
      "flowerCount": 2
    }
]

是的,你是对的。 虚构的 GET /founders 端点直接返回一个列表。 JSON 以 [] 开头和结尾。 你也说得对,Marcus 喜欢在办公桌的绿色丛林中工作。

那么我们如何将 Gson 映射到 Java 对象呢? 第一步是创建我们的模型:

public class Founder {  
    String name;
    int flowerCount;
}

第二步取决于你。 你是想使用列表或数组作为自己的类型吗?

数组

如果你想使用数组,这很简单。 可以像我们之前所做的那样直接使用 fromJson() 函数,并将模型类作为数组传递,例如 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);  

这将创建一个创建者对象的 Java 数组,这些对象具有正确映射的属性

Gson — 数组和对象列表的映射

列表

由于包含有更多的方法,现在许多开发人员更喜欢 Java 列表。 不幸的是,我们不能直接将 List<Founder> 传递给 Gson。 为了让 Gson 正确理解 List 结构,你必须弄清楚它的 Type。 幸运的是,有一个 Gson 类 TypeToken 可以帮助我们为几乎任何类配置找到正确的 Type 。 对于 ArrayList 中的 Founder 类,它看起来像这样:

Type founderListType = new TypeToken<ArrayList<Founder>>(){}.getType();  

我们可以将语句的结果用作 Gson 调用的类型:

String founderJson = "[{'name': 'Christian','flowerCount': 1}, {'name': 'Marcus', 'flowerCount': 3}, {'name': 'Norman', 'flowerCount': 2}]";

Gson gson = new Gson();

Type founderListType = new TypeToken<ArrayList<Founder>>(){}.getType();

List<Founder> founderList = gson.fromJson(founderJson, founderListType);  

这与 Array 方法一样有效:

Gson — 数组和对象列表的映射

最后,如果我们将数据映射到数组或列表,这取决于大家的个人喜好和用例。 让我们再看一个主题:如何反序列化嵌套在对象中的列表:


作为对象的一部分的列表

我们用另一个通用端点 GET /info 扩展了我们想象中的 迹忆客 API 。 它返回的不仅仅是 Founder 信息:

{
  "name": "迹忆客美食馆m",
  "website": "https://www.jiyik.com",
  "founders": [
    {
      "name": "Christian",
      "flowerCount": 1
    },
    {
      "name": "Marcus",
      "flowerCount": 3
    },
    {
      "name": "Norman",
      "flowerCount": 2
    }
  ]
}

首先,我们必须为 JSON 响应创建一个合适的模型。 我们可以重用上一节中的 Founder 类:

public class GeneralInfo {  
    String name;
    String website;
    List<Founder> founders;
}

嵌套在对象中的列表的优点是简单的 Gson 调用,无需任何 TypeToken 处理。 我们可以直接传类:

String generalInfoJson = "{'name': '迹忆客美食馆', 'website': 'https://www.jiyik.com', 'founders': [{'name': 'Christian', 'flowerCount': 1 }, {'name': 'Marcus','flowerCount': 3 }, {'name': 'Norman','flowerCount': 2 }]}";

Gson gson = new Gson();

GeneralInfo generalInfoObject = gson.fromJson(generalInfoJson, GeneralInfo.class);

这将产生一个完整的对象:

Gson — 数组和对象列表的映射

当然,我们可以将 GeneralInfo 类更改为使用 Founder[] 数组而不是 List<Founder> 。 Gson 调用将保持不变。

注意 :不知道大家是否注意到 Gson 对具有 name 属性的 GeneralInfo 和 Founder 模型都没有问题? 它会毫无问题地对其进行序列化和反序列化。 这是多么惊人!


嵌套在列表中的列表

Gson 对嵌套在列表中的列表是也正常映射。 例如,以下模型没有问题:

public class GeneralInfo {  
    String name;
    String website;
    List<FounderWithPets> founders;
}

FounderWithPets 类现在稍微扩展了一个宠物列表:

public class FounderWithPets {  
    String name;
    int flowerCount;
    List<Pet> pets;
}

另一方面, Pet 类包括一系列玩具:

public class Pet {  
    String name;
    List<Toy> toys;
}

Toy 类包括……好吧,我们现在停止。 我们希望这能说明这一点:我们可以将列表包装在列表中(甚至不止一次)而不会出现任何问题。 Gson 在处理序列化和反序列化方面简直就是一个世界冠军。

尽管如此,Gson 只能处理具有一定一致性的对象列表。 如果对象是完全随机和任意的,Gson 将无法映射它们! 不过,多态对象列表不是问题。

在这篇博文中,我们了解了 Gson 如何能够毫无问题地映射列表(或数组)数据。 Gson 很灵活,在 Java 端接受 List 和 Array 实现。 我们需要学习如何从 JSON 结构中识别列表是根还是嵌套在对象中。 我们还学习了如何设置从 JSON 到 Java 数组或 Java 列表的不同反序列化。