This article is originally published at https://swiftsenpai.com on May 30, 2020.
本文最初于 2020年5月30日 发布在 https://swiftsenpai.com 上。
The Decodable
protocol was introduced in Swift 4. Since then it has become the standard way for developers to decode JSON received from a remote server.
Swift 4中引入了Decodable
协议。此后,它已成为开发人员解码从远程服务器接收的JSON的标准方法。
There are tons of tutorials out there that teach you how to utilize the Decodable
protocol to decode various types of JSON structure. However, all these tutorials do not cover one specific type of JSON structure - JSON with dynamic keys.
那里有大量的教程,教您如何利用Decodable
协议解码各种类型的JSON结构。 但是,所有这些教程都不涵盖一种特定类型的JSON结构- 具有动态键的JSON 。
What do I mean by JSON with dynamic keys? Take a look at the following sample JSON that shows a list of students:
带有动态键的JSON是什么意思? 看一下以下显示学生列表的示例JSON:
As you can see, the student ID is the key and the student information is the value.
如您所见,学生ID是密钥,而学生信息是值。
As iOS developers, what we usually need is an array of students, so that the list of students can be easily displayed on a table view. Therefore, aside from decoding the JSON, we also need to flatten the result (make student ID part of the student object) and transform it into an array.
作为iOS开发人员,我们通常需要的是一群学生,以便可以在表格视图中轻松显示学生列表。 因此,除了解码JSON之外,我们还需要将结果展平(使Student ID成为Student对象的一部分)并将其转换为数组。
When facing such JSON structure, some developers might fall back to the old decoding approach by using the JSONSerialization
class and manually looping through and parsing each and every key-value pair.
当面对这样的JSON结构时,一些开发人员可能会通过使用JSONSerialization
类并手动循环遍历和解析每个键值对来回到旧的解码方法。
However, I do not like the JSONSerialization
way because it is more error-prone. On top of that, we will lose all the benefits that come along with using Decodable
protocol.
但是,我不喜欢JSONSerialization
方式,因为它更容易出错。 最重要的是,我们将失去使用Decodable
协议带来的所有好处 。
In this article, I will walk you through the decoding approach that utilizes the Decodable
protocol. After that, I will make the decoding logic generic so that it can be reused by some other object type.
在本文中,我将向您介绍利用Decodable
协议的解码方法。 之后,我将使解码逻辑具有通用性,以便可以由其他一些对象类型重用。
With all that being said, let’s get right into it.
话虽这么说,让我们开始吧。
提取价值 (Extracting the Values)
As a recap, this is the JSON that we trying to decode:
作为回顾,这是我们尝试解码的JSON:
For simplicity sake, let’s focus on decoding the firstName
and lastName
for now. We will get back to the student ID later.
为了简单起见,让我们现在集中在解码firstName
和lastName
上。 稍后我们将返回到学生证。
First, let’s define a Student
struct that conforms to the Decodable
protocol.
首先,让我们定义一个符合Decodable
协议的Student
结构。
Next, we will need a Decodable
struct that contains a Student
array. We will use this struct to hold all the decoded Student
objects. Let's call this struct DecodedArray
.
接下来,我们将需要一个包含Student
数组的Decodable
结构。 我们将使用此结构来保存所有已解码的Student
对象。 我们将此结构DecodedArray
。
In order to access the JSON’s dynamic keys, we must define a custom CodingKey
struct. This custom CodingKey
struct is needed when we want to create a decoding container from the JSONDecoder
.
为了访问JSON的动态键,我们必须定义一个自定义的CodingKey
结构。 当我们要从JSONDecoder
创建解码容器时,需要此自定义CodingKey
结构。
Note that we are only interested in the string value initializer because our keys are of type string, therefore we can just return nil in the integer value initializer.
注意,我们只对字符串值初始化程序感兴趣,因为我们的键是string类型的,因此我们可以在整数值初始化程序中返回nil。
With all that in place, we can now start implementing the DecodedArray
initializer.
有了所有这些之后,我们现在就可以开始实现DecodedArray
初始化程序了。
Let’s break down in detail what’s happening inside the initializer.
让我们详细分析初始化器中发生的事情。
Create a decoding container using the
DynamicCodingKeys
struct. This container will contain all the JSON's first level dynamic keys.使用
DynamicCodingKeys
结构创建一个解码容器。 此容器将包含JSON的所有第一级动态键。Loop through each key to decode its respective
Student
object.遍历每个键以解码其各自的
Student
对象。Store all the decoded
Student
objects into theStudent
array.将所有已解码的
Student
对象存储到Student
数组中。
That’s it for extracting firstName
and lastName
. Let's run all these in the Xcode playground to see them in action.
提取firstName
和lastName
。 让我们在Xcode游乐场中运行所有这些命令,以查看它们的实际效果。
Here we convert the JSON string to data and ask the JSONDecoder
to decode the JSON data as DecodedArray
type. With that, we will be able to access all the decoded Student
objects via DecodedArray.array
.
在这里,我们将JSON字符串转换为数据,并要求JSONDecoder
将JSON数据解码为DecodedArray
类型。 这样,我们将能够通过DecodedArray.array
访问所有已解码的Student
对象。
Congratulations! You have successfully decoded all the Student
objects. However, there are still works to do. In the next section, we will look into adding the student ID into the Student
struct.
恭喜你! 您已经成功解码了所有Student
对象。 但是,仍有工作要做。 在下一节中,我们将研究将学生ID添加到Student
结构中。
提取密钥 (Extracting the Keys)
With what we currently have, adding the student ID into the Student
struct is pretty straightforward. What we need to do is implement our own Student
initializer and manually decode lastName
, firstName
and studentId
.
根据目前的情况,将学生ID添加到Student
结构中非常简单。 我们需要做的是实现我们自己的Student
初始化程序,并手动解码lastName
, firstName
和studentId
。
Take a look at the following updated Student
struct:
看一下以下更新的Student
结构:
Let’s go through the changes we made on Student
struct one by one:
让我们逐一查看对Student
结构所做的更改:
Define
studentId
to hold the extracted key (student ID).定义
studentId
以保存提取的键(学生ID)。- Define coding keys that are needed for manual decoding. 定义手动解码所需的编码密钥。
Manually decode
firstName
andlastName
.手动解码
firstName
和lastName
。This is where the magic happens. The decoding container
codingPath
is an array ofCodingKey
that contains the path of coding keys taken to get to this point in decoding. For our case, it should contain the key we obtained fromDynamicCodingKeys
inDecodedArray
, which is the student ID.这就是魔术发生的地方。 解码容器
codingPath
是阵列CodingKey
包含编码采取得到这一点在解码密钥的路径。 对于我们的情况,它应包含我们从DecodedArray
DynamicCodingKeys
获得的密钥,即学生ID。
Let’s run this again in Xcode playground to see the final result.
让我们在Xcode操场上再次运行它以查看最终结果。
With that, we have successfully decoded and flattened a JSON with dynamic keys using the Decodable
protocol. 🥳
这样,我们就可以使用Decodable
协议使用动态密钥成功解码和展平JSON。 🥳
In the next section, let’s go one step further by improving the DecodedArray
struct functionality and reusability.
在下一节中,我们将通过改善DecodedArray
结构的功能性和可重用性来进一步DecodedArray
。
添加自定义集合支持 (Adding Custom Collection Support)
If you take a closer look into the DecodedArray
struct, it is basically just a wrapper of the Student
array. This makes it the perfect candidate to transform into a custom collection.
如果仔细研究DecodedArray
结构,它基本上只是Student
数组的包装。 这使其成为转换为自定义集合的理想人选。
By transforming into a custom collection, the DecodedArray
struct can take advantage of the array literal, as well as all the standard collection functionalities such as filtering and mapping.
通过转换为自定义集合, DecodedArray
结构可以利用数组文字以及所有标准集合功能(例如过滤和映射)的优势。
First, let’s define a typealias
to represent the Student
array and update the other part of DecodedArray
accordingly. The typealias
is required when we conform the DecodedArray
to the Collection
protocol later.
首先,我们定义一个类型typealias
来表示Student
数组,并相应地更新DecodedArray
的另一部分。 该typealias
当我们符合了要求DecodedArray
到Collection
协议后。
Here’s the updated DecodedArray
where I have marked the changes made with ***
.
这是更新的DecodedArray
,其中已标记了***
所做的更改。
Next up, let’s extend the DecodedArray
and conform to the Collection
protocol.
接下来,让我们扩展DecodedArray
并遵循Collection
协议。
The details of conforming to the Collection
protocol are beyond the scope of this article. If you want to know more, I highly recommend this great article.
符合Collection
协议的详细信息不在本文的讨论范围之内。 如果您想了解更多,我强烈推荐这篇很棒的文章 。
That’s about it, we have fully transformed the DecodedArray
struct into a custom collection.
就是这样,我们已经将DecodedArray
结构完全转换为自定义集合。
Once again, let’s test out our changes in the Xcode playground. But this time with some cool functionalities that we gain from the Collection
protocol conformance - array literal, map
, and filter
.
再次让我们在Xcode游乐场中测试我们的更改。 但是这一次我们可以从Collection
协议一致性中获得一些很酷的功能-数组文字, map
和filter
。
Pretty cool isn’t it? With a little bit of extra effort, let’s make it even cooler by making the DecodedArray
generic so that we can reuse it on other object types.
是不是很酷? 花费一些额外的精力,让我们通过使DecodedArray
泛型使其更酷,以便我们可以在其他对象类型上重用它。
使其通用,提高可重用性 (Make It Generic, Increase Reusability)
To make our DecodedArray
generic, we just need to add a generic parameter clause and replace all the Student
type with a placeholder type T
.
为了使我们的DecodedArray
通用,我们只需要添加一个通用参数子句,并用占位符类型T
替换所有Student
类型。
Once again, I have marked all the changes with ***
.
我再次用***
标记了所有更改。
With all this in place, we can now use it to decode any object types. To see that in action, let’s use our generic DecodedArray
to decode the following JSON.
完成所有这些操作后,我们现在可以使用它来解码任何对象类型。 为了看到实际效果,让我们使用通用的DecodedArray
解码以下JSON。
The above JSON represents an array of Food
objects grouped by category. Thus, we must first define the Food
struct.
上面的JSON表示按类别分组的Food
对象数组。 因此,我们必须首先定义Food
结构。
After defining the Food
struct, we are now ready to decode the given JSON using the generic DecodedArray
.
定义Food
结构之后,我们现在可以使用通用的DecodedArray
解码给定的JSON。
Do note that decodedResult
is an array of Food
arrays ([[Food]]
). Therefore, to get an array of Food
objects ([Food]
), we will apply flatmap
on decodedResult
to convert [[Food]]
to [Food]
.
请注意, decodedResult
是一个Food
数组( [[Food]]
)的数组。 因此,为了获得数组Food
对象( [Food]
),我们将申请flatmap
上decodedResult
转换[[Food]]
至[Food]
。
结语 (Wrapping Up)
This article only demonstrates decoding and flattening JSON with 2 layers, you can definitely apply the same concept on JSON with 3 or more layers. I’ll leave that as an exercise for you!
本文仅演示了解码和平坦化2层JSON,您肯定可以在3层或更多层的JSON上应用相同的概念。 我将其留给您练习!
If you would like to try out the decoding approach on Xcode playground, here’s the full sample code.
如果您想在Xcode操场上尝试解码方法,请参阅完整的示例代码 。
What do you think about this decoding approach? Feel free to leave your comments or thoughts in the comment section below.
您如何看待这种解码方式? 随时在下面的评论部分中留下您的评论或想法。
If you like this article, make sure to check out my other articles related to Swift.
如果您喜欢本文,请确保查看我与Swift相关的其他文章 。
You can also follow me on Twitter for more articles related to iOS development.
您也可以在Twitter上关注我,以获取有关iOS开发的更多文章。
Thanks for reading. 🧑🏻💻
谢谢阅读。 💻
进一步阅读 (Further Readings)
翻译自: https://medium.com/swlh/how-to-decode-this-json-using-swift-decodable-315464b0b1af