Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在 Swift 中对 JSON 进行自定义编码和解码的小技巧 #6967

Merged
merged 63 commits into from
Apr 30, 2020
Merged
Changes from 45 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
afb007d
更新测试
chaingangway Mar 30, 2020
a261e82
Revert "更新测试"
chaingangway Mar 30, 2020
862cc57
8 个值得了解的树形数据结构 (#6804)
Amberlin1970 Mar 27, 2020
0632846
眼动跟踪和移动世界的最佳用户体验实践 (#6806)
Charlo-O Mar 27, 2020
a66e069
我并不讨厌箭头函数(#6610) (#6659)
TiaossuP Mar 27, 2020
28c79b4
掌握 JavaScript 面试:什么是纯函数? (#6828)
nia3y Mar 28, 2020
57bd9bd
fix:文章翻译问题 (#6833)
fireairforce Mar 28, 2020
95eec4a
用依赖注入来解耦你的代码 (#6823)
JalanJiang Mar 29, 2020
e4c0cac
Create generator-functions-in-javascript.md (#6841)
lsvih Mar 31, 2020
0465602
Create how-to-keep-your-dependencies-secure-and-up-to-date.md (#6843)
lsvih Mar 31, 2020
bc64328
Create deep-dive-into-react-fiber-internals.md (#6845)
lsvih Mar 31, 2020
e4759ff
Update deep-dive-into-react-fiber-internals.md
lsvih Mar 31, 2020
4e78a12
Create how-can-cloud-services-help-improve-your-businessess-efficienc…
lsvih Mar 31, 2020
ae7da28
Create should-you-learn-vim-as-a-developer-in-2020.md (#6849)
lsvih Mar 31, 2020
6738a6e
Go 发布新版 Protobuf API (#6827)
Mar 31, 2020
d884798
JSON.stringify() 的 5 个秘密特性 (#6793)
fireairforce Mar 31, 2020
d04aff3
Create high-speed-inserts-with-mysql.md (#6853)
lsvih Apr 1, 2020
85cc9e1
组合软件:书 (#6832)
fireairforce Apr 2, 2020
d8fc4d3
Create 6-best-javascript-frameworks-in-2020.md (#6861)
lsvih Apr 3, 2020
a8c74da
2020 年用各大前端框架构建的 RealWorld 应用对比 (#6851)
jasperxihuan Apr 4, 2020
77f1fb3
Create the-importance-of-why-docs.md (#6868)
lsvih Apr 6, 2020
8260ddc
不变性之道 (#6857)
nia3y Apr 6, 2020
548dfe6
Create polymorphic-react-components.md (#6870)
lsvih Apr 6, 2020
b8ba5b2
Create active-learning-in-machine-learning.md (#6872)
lsvih Apr 6, 2020
509ad30
Create kafka-vs-rabbitmq-why-use-kafka.md (#6874)
lsvih Apr 6, 2020
bc01bdc
Create i-built-an-app-that-uses-all-7-new-features-in-javascript-es20…
lsvih Apr 6, 2020
d278484
云服务如何帮助你提高业务效率? (#6859)
QinRoc Apr 7, 2020
d250ddb
NestJS 实现基本用户认证和会话 (#6731)
eizyc Apr 7, 2020
b1a3030
作为 2020 年的开发者,你应该学习 VIM 吗? (#6856)
chaingangway Apr 7, 2020
da77d74
MySQL 最佳实践—— 高效插入数据 (#6863)
Apr 8, 2020
5531303
Create combine-getting-started.md (#6883)
lsvih Apr 9, 2020
69ae6a6
Create how-to-be-a-good-remote-developer.md (#6885)
lsvih Apr 9, 2020
6c210ce
Create why-is-object-immutability-important.md (#6887)
lsvih Apr 9, 2020
e0b042e
Create what-on-earth-is-the-shadow-dom-and-why-it-matters.md
lsvih Apr 9, 2020
cc61cef
2020 年排名前 6 位的 JavaScript 框架 (#6867)
QinRoc Apr 9, 2020
53c6ba1
JavaScript 风格元素 (#6878)
febrainqu Apr 9, 2020
533bef9
怎样让依赖库保持安全和最新 (#6864)
chaingangway Apr 9, 2020
ff11c6e
Create 5-best-practices-to-prevent-git-leaks.md (#6894)
lsvih Apr 10, 2020
e3a066a
Create swiftui-3d-scroll-effect.md (#6896)
lsvih Apr 10, 2020
7153da6
Web 应用程序中的数据和 UI 分离 (#6794)
fireairforce Apr 10, 2020
e113faf
fix:typo (#6903)
fireairforce Apr 12, 2020
ce21ade
Merge remote-tracking branch 'upstream/master'
chaingangway Apr 15, 2020
2adbc9c
Merge remote-tracking branch 'upstream/master'
chaingangway Apr 22, 2020
b9ec935
Merge remote-tracking branch 'upstream/master'
chaingangway Apr 26, 2020
c999d2c
初译完成
chaingangway Apr 26, 2020
d2f5e1e
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
85ad4ff
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
3364670
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
3358635
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
b5fe0b8
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
b8c3ec0
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
5d1e1d7
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
ab2d3de
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
21a6510
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
63accac
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
06fd342
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
ab15460
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
1b3049d
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
ad6d2f6
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
6bcf43d
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
a1d2d99
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
3cac872
Update TODO1/custom-encoding-and-decoding-json-in-swift.md
chaingangway Apr 30, 2020
bf11d1a
增加校对者信息
chaingangway Apr 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 72 additions & 72 deletions TODO1/custom-encoding-and-decoding-json-in-swift.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
> * 原文作者:[Leandro Fournier](https://medium.com/@lean4nier)
> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/custom-encoding-and-decoding-json-in-swift.md](https://github.com/xitu/gold-miner/blob/master/TODO1/custom-encoding-and-decoding-json-in-swift.md)
> * 译者:
> * 译者:[chaingangway](https://github.com/chaingangway)
> * 校对者:
lsvih marked this conversation as resolved.
Show resolved Hide resolved

# Custom encoding and decoding JSON in Swift
# 在 Swift 中对 JSON 进行自定义编码和解码的小技巧

![](https://cdn-images-1.medium.com/max/2000/0*-t2P3atrbgHMKR6P.jpg)

> This post was originally written by me at [Swift Delivery](https://www.leandrofournier.com/custom-encoding-and-decoding-json/).
本文我最早发表在 [Swift Delivery](https://www.leandrofournier.com/custom-encoding-and-decoding-json/)

In our last [Working with JSON in Swift series](https://levelup.gitconnected.com/working-with-json-in-swift-c5faea0b19a1), we explore various items:
在最近的 [Working with JSON in Swift series](https://levelup.gitconnected.com/working-with-json-in-swift-c5faea0b19a1) 文章中,我们学习了这些知识点:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

* `Codable` protocol, which contains two other protocols: `Encodable` and `Decodable`
* How to decode a JSON data object into a readable Swift struct
* Usage of custom keys
* Custom objects creation
* Arrays
* Different top level entities
* `Codable` 协议,它还包含另外两个协议: `Encodable` `Decodable`
* 如何将 JSON 数据对象解析成具有可读性的 Swift 结构体。
* 自定义 key 的使用。
* 自定义对象的创建。
* 数组
* 各种一级实体

That’s enough for a basic usage of JSON in Swift, which will enable us to read JSON data (decode) and create a new object which can be converted back to JSON (encode) and send it, for instance, to a RESTFul API.
通过了解这些,你能掌握 Swift 中 JSON 的基本使用。比如,你可以读取 JSON 数据(解码),创建可以被转换为 JSON 格式的对象(编码),然后把这个对象发送给 RestFul API
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

So, first things first: let’s create an object and convert it to a JSON data format.
首先,我们来创建一个对象,把它转换成 JSON 数据格式。

## Encoding
## 编码

#### Default encoding
#### 默认编码

Let’s assume we have the following `struct` for insects:
下面的代码中定义了 Insect 结构体:

```swift
struct Insect: Codable {
Expand All @@ -44,22 +44,22 @@ struct Insect: Codable {
}
```

To sum up, we have three properties. **insectId** for the insect identifier, **name** for its name and **isHelpful** to specify if the insect is helpful or not to our garden. Two of these properties use custom keys (**insectId** and **isHelpful**).
结构体中一共有三个属性。**insectId** 表示昆虫的身份,**name** 表示昆虫的名称,**isHelpful** 表示昆虫是否对我们的花园有益。其中两个属性使用了自定义 key (**insectId** **isHelpful**)
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

Now let’s create an insect:
现在我们新建一个昆虫实例:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
let newInsect = Insect(insectId: 1006, name: "ants", isHelpful: true)
```

Our RESTful API expects to receive a JSON with this new insect information. Then we have to encode it:
我们的 RESTFul API 需要接收 JSON 格式的昆虫数据,所以我们需要对它进行编码:

```swift
let encoder = JSONEncoder()
let insectData: Data? = try? encoder.encode(newInsect)
```

That was easy: now **insectData** is an object of type `Data?`. We might want to check whether the encoding actually worked (just a check, you probably won't do this in your code). Let's rewrite the code above and use some optional unwrapping:
这一步很简单: 现在 **insectData** 已经是 `Data?` 类型。我们可能还想检查一下编码是否真的生效了(只是一个验证,你在写代码时可以不必这样)。我们用解包的方式来重构上面的代码:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
let encoder = JSONEncoder()
Expand All @@ -70,21 +70,21 @@ if let insectData = try? encoder.encode(newInsect),
}
```

1. Create the encoder
2. Try to encode the object we’ve created
3. Convert, if possible, the `Data` object into a `String`
1. 创建 encoder
2. 尝试对我们创建的对象编码。
3. 进行转换,如果编码成功的话,`Data` 对象会变成 `String` 类型。

Then we print out the result which will look like this:
我们打印一下结果,它的格式如下:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```json
{"name":"ants","is_helpful":true,"insect_id":1006}
```

> Note that the keys used while encoding are not the custom keys (**insectId** and **isHelpful**) but the expected keys (**insect_id** and **is_helpful**). Nice!
> 注意编码时的 key 不是自定义的 key(**insectId** **isHelpful**),而是我们希望的 (**insect_id** **is_helpful**)。太棒了!
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

#### Custom encoding
#### 自定义编码

Let’s suppose our RESTful API expects to receive the name of the insect uppercased. We need to create our own implementation of the encoding method so to make sure the name of the insect is sent uppercased. We must implement the method **func** encode(to encoder: Encoder) **throws** of the `Encodable` protocol inside our **Insect** `struct`.
假设 RESTful API 需要接受大写名称的昆虫数据。我们就需要实现自己的编码方法,来保证昆虫名称是大写的。要这样做的话,我们就必须在 **Insect** 结构体中实现 `Encodable` 协议中的 **func** encode(to encoder: Encoder) **throws** 方法。

```swift
struct Insect: Codable {
Expand All @@ -99,31 +99,31 @@ struct Insect: Codable {
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(insectId, forKey: .insectId)
try container.encode(name.uppercased(), forKey: .name)
try container.encode(isHelpful, forKey: .isHelpful)
var container = encoder.container(keyedBy: CodingKeys.self) // 13
try container.encode(insectId, forKey: .insectId) // 14
try container.encode(name.uppercased(), forKey: .name) // 15
try container.encode(isHelpful, forKey: .isHelpful) // 16
}
}
```

Line 13 is where we create a container where encoded values will be stored. The container MUST be a `var` because it's mutable and MUST receive the keys to be used.
13 行我们创建了一个用于存储编码数据的容器。这个容器必须是 `var` 类型,它要接收这些 key,因此是可变类型的。

Lines 14 to 16 are used to encode the values into the container, which is done using `try` because any of these can throw an error.
14 16 行是把数据进行编码后,存储到容器中。每一次存储都可能抛出异常,所以这里用到了 `try`

Now, look at line 15: we don’t just put the value as it is but we uppercase it, which is main reason we’re implementing a custom encoding.
现在,看第 15 行: 我们没有原封不动地把数据存储,而是对其进行了大写处理。这就是我们要实现自定义编码的主要原因。
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

If you run the code above, where we create the **Insect** “ants”, we’ll see that after encoding and converting the resulting JSON `Data` into a `String` we get the following:
运行上面的代码后,你会发现 **Insect** 中的 “ants” 属性在编码转换成 JSON 字符串后,格式是下面这样的:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```
{"name":"ANTS","is_helpful":true,"insect_id":1006}
```

As you might have seen, the name of the insect is now uppercased despite we’ve created it lowercased. How cool is that!
即使昆虫名称的初始值是小写的,现在它的名称也变成了大写。这太酷了!

## Custom decoding
## 自定义解码

So far we’ve been relying on the default decoding method of the `Decodable` protocol. But let's take a look at another scenario.
目前为止,我们一直都依赖 `Decodable` 协议中默认的解码方法。下面我们来看看另外的方法:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```json
[
Expand Down Expand Up @@ -172,70 +172,70 @@ So far we’ve been relying on the default decoding method of the `Decodable` pr
]
```

The API is retrieving the **is_helpful** property inside a **details** entity. But we don’t want to create a **Details** object: we just want to flatten that object so we can use our existing **Insect** object.
API 获取的 **is_helpful** 属性在 **details** 实体内部。但是我们不想创建 **Details** 对象: 我们只想展开它,这样就可以直接用现有的 **Insect** 对象了。
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

Time to use our own implementation of the **init**(from decoder: Decoder) **throws** method of the `Decodable` protocol and some extra work. Let's get started.
现在我们要实现 `Decodable` 协议中的 **init**(from decoder: Decoder) **throws** 方法,然后做一些额外处理。

First of all, coding keys has changed because **is_helpful** is not a key in the same level as before AND there’s a new one called **details**. To fix that:
首先,key 不一样了,**is_helpful** 不是同级的 key了,这里新的 key **details**。我们这样编写代码:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
enum CodingKeys: String, CodingKey {
case insectId = "insect_id"
case name
case details
case details // 4
}

enum DetailsCodingKeys: String, CodingKey {
case isHelpful = "is_helpful"
}
enum DetailsCodingKeys: String, CodingKey { // 7
case isHelpful = "is_helpful" // 8
} // 9
```

In line 4 we replace the existing key with the new one, **details**.
第 4 行,我们用 **details** 替换了之前的 key。

In lines 7 and 9 we create a new set of keys, the ones that exist inside **details**, which in this case is just one, **isHelpful**.
第 7 行到第 9 行,我们新建了一个枚举,对应 **details** 内部的 key,在本例中,只有一个 **isHelpful**

> Note that we didn’t touch the properties of the **Insect** `struct`.
> 注意我们还没有接触到 **Insect** `结构体`的属性。

Now let’s dive into the decoder initialization:
下面我们深入了解一下解码的初始化方法:

```swift
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let container = try decoder.container(keyedBy: CodingKeys.self) // 2

insectId = try container.decode(Int.self, forKey: .insectId)
name = try container.decode(String.self, forKey: .name)
let details = try container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details)
isHelpful = try details.decode(Bool.self, forKey: .isHelpful)
insectId = try container.decode(Int.self, forKey: .insectId) // 4
name = try container.decode(String.self, forKey: .name) // 5
let details = try container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) // 6
isHelpful = try details.decode(Bool.self, forKey: .isHelpful) // 7
}
```

In line 2 we create the container which decodes the whole JSON structure.
第 2 行中我们创建了用于解析整个 JSON 结构的容器。

In line 4 and 5 we just decode the `Int` value for the **insectId** property and the `String` value for the **name** property.
第 4 行和第 5 行,我们解析了 **insectId** 属性的 `Int` 数据和 **name** 属性的 `String` 数据。

In line 6 we grab the nested container under the **details** key which is keyed by the brand new **DetailsCodingKeys** `enum`.
第 6 行,我们获得了 **details** key 内部的容器,容器内的 key 是通过 **DetailsCodingKeys** `枚举`创建的。

In line 7 we just decode the `Bool` value for the **isHelpful** property inside our new **details** container.
第 7 行,我们在 **details** 的容器中解析了 **isHelpful** 属性的 `Bool` 数据。

BUT that’s not it. Since our **CodingKeys** has changed by adding the **details** case, our custom encoding implementation must be fixed. So let’s do that:
但是事情还没有做完。我们在 **CodingKeys** 中加入了 **details**,所以我们自定义的编码方法也要如下修改:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(insectId, forKey: .insectId)
try container.encode(name.uppercased(), forKey: .name)
var details = container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details)
try details.encode(isHelpful, forKey: .isHelpful)
var details = container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) // 5
try details.encode(isHelpful, forKey: .isHelpful) // 6
}
```

We just changed the way we encode the **isHelpful** property.

In line 5 we create a new nested container, using the keys inside the **DetailsCodingKeys** `enum` and to be used inside the **details** entity inside our JSON.
我们只用修改 **isHelpful** 属性的编码方式就可以了。
在第 5 行中我们用 **DetailsCodingKeys** `枚举`作为 key 创建了一个内部容器,用于在 **details** 实体的内部使用。

In line 6 we encode **isHelpful** INSIDE the brand new **details** nested container.
第 6 行我们在新建的 **details** 内部容器中对 **isHelpful** 进行编码。

So, to sum up, our **Insect** `struct` looks like this:
所以,最终的 **Insect** `结构体`是这样的:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
struct Insect: Codable {
Expand Down Expand Up @@ -273,7 +273,7 @@ struct Insect: Codable {
}
```

If we decode it:
下面开始解码:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```swift
let decoder = JSONDecoder()
Expand All @@ -282,21 +282,21 @@ if let insects = try? decoder.decode([Insect].self, from: jsonData!) {
}
```

We’ll get something like this:
我们会得到下面的结果:
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

```
[__lldb_expr_54.Insect(insectId: 1001, name: "BEES", isHelpful: true), __lldb_expr_54.Insect(insectId: 1002, name: "LADYBUGS", isHelpful: true), __lldb_expr_54.Insect(insectId: 1003, name: "SPIDERS", isHelpful: true), __lldb_expr_54.Insect(insectId: 2001, name: "TOMATO HORN WORMS", isHelpful: false), __lldb_expr_54.Insect(insectId: 2002, name: "CABBAGE WORMS", isHelpful: false), __lldb_expr_54.Insect(insectId: 2003, name: "CABBAGE MOTHS", isHelpful: false)]
```

As you can see, there’s no **details** entity: just our `struct` properties.
你可以看到,并没有 **details** 实体: 只有 `结构体` 的属性。
chaingangway marked this conversation as resolved.
Show resolved Hide resolved

The encoding will work as expected as well.
编码流程也是正常的。

This post, plus the previous series, sums up pretty much the most common scenarios you may run into when working with JSON in Swift.
本文和之前的一系列文章,总结了很多在 Swift 中处理 JSON 最常见的场景。

## Need more information?
## 更多内容

Ben Scheirman’s [Ultimate Guide to JSON Parsing with Swift](https://benscheirman.com/2017/06/swift-json/) is the most useful source I could find for this subject.
推荐 Ben Scheirman 写的 [Ultimate Guide to JSON Parsing with Swift](https://benscheirman.com/2017/06/swift-json/) 这篇文章,它是与本文主题相关最实用的资料。

> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。

Expand Down