Demonstrates approaches for encoding and decoding different kinds of JSON in Swift.
演示在Swift中编码和解码不同类型的JSON的方法。
Overview
JSON data you send or receive from other apps, services, and files can come in many different shapes and structures. Use the techniques described in this sample to handle the differences between external JSON data and your app’s model types.
您从其他应用程序,服务和文件发送或接收的JSON数据可以有许多不同的形状和结构。 使用此示例中描述的技术来处理外部JSON数据与应用程序模型类型之间的差异。
This sample defines a simple data type, GroceryProduct, and demonstrates how to construct instances of that type from several different JSON formats.
此示例定义了一个简单的数据类型GroceryProduct,并演示了如何从几种不同的JSON格式构造该类型的实例。
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Read Data from Arrays
Use Swift’s expressive type system to avoid manually looping over collections of identically structured objects. This playground uses array types as values to see how to work with JSON that’s structured like this:
使用Swift的表达式系统来避免手动循环相同结构对象的集合。 这个游乐场使用数组类型作为值来查看如何使用结构如下的JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
}
]
Change Key Names
Learn how to map data from JSON keys into properties on your custom types, regardless of their names. For example, this playground shows how to map the "product_name" key in the JSON below to the name property on GroceryProduct:
了解如何将JSON 键中的数据映射到你自定义类型的属性中,无论其名称如何。 例如,此游乐场显示如何将下面JSON中的“product_name”键映射到GroceryProduct上的name属性:
{
"product_name": "Banana",
"product_cost": 200,
"description": "A banana grown in Ecuador."
}
Custom mappings let you to apply the Swift API Design Guidelines to the names of properties in your Swift model, even if the names of the JSON keys are different.
通过自定义映射,您可以将Swift API设计指南应用于Swift模型中的属性名称,即使JSON键的名称不同也是如此。
Access Nested Data
Learn how to ignore structure and data in JSON that you don’t need in your code. This playground uses an intermediate type to see how to extract grocery products from JSON that looks like this to skip over unwanted data and structure:
了解如何忽略代码中不需要的JSON结构和数据。 这个游乐场使用中间类型来查看如何从JSON中提取杂货产品,看起来像这样跳过不需要的数据和结构:
[
{
"name": "Home Town Market",
"aisles": [
{
"name": "Produce",
"shelves": [
{
"name": "Discount Produce",
"product": {
"name": "Banana",
"points": 200,
"description": "A banana that's perfectly ripe."
}
}
]
}
]
}
]
Merge Data at Different Depths
Combine or separate data from different depths of a JSON structure by writing custom implementations of protocol requirements from Encodable and Decodable. This playground shows how to construct a GroceryProduct instance from JSON that looks like this:
通过编写来自Encodable和Decodable的协议要求的自定义实现,组合或分离来自JSON结构的不同深度的数据。 这个游乐场展示了如何从JSON构建一个GroceryProduct实例,如下所示:
{
"Banana": {
"points": 200,
"description": "A banana grown in Ecuador."
}
}
示例:
When you control the structure of your data types in Swift as well as the structure of the JSON you use for encoding and decoding, use the default format generated for you by the JSONEncoder and JSONDecoder classes.
当您在Swift中控制数据类型的结构和用于编码和解码的JSON结构一样时,请使用JSONEncoder和JSONDecoder类为您生成的默认格式。
However, when the data your code models follows a specification that shouldn't be changed, you should still write your data types according to Swift best practices and conventions. This sample shows how to use a type's conformance to the Codable protocol as the translation layer between the JSON representation of data and the corresponding representation in Swift.
但是,当代码模型的数据遵循不应更改的规范时,您仍应根据Swift最佳实践和约定编写数据类型。 此示例演示如何使用类型与Codable协议的一致性作为数据的JSON表示与Swift中的相应表示之间的转换层。
1.Read Data From Arrays
When the JSON you use contains a homogeneous array of elements, you add a conformance to the Codable protocol on the individual element's type. To decode or encode the entire array, you use the syntax [Element].self.
当您使用的JSON包含同构数组元素时,您可以在单个元素的类型上添加Codable协议的一致性。 要解码或编码整个数组,请使用语法[Element] .self。
In the example below, the GroceryProduct structure is automatically decodable because the conformance to the Codable protocol is included in its declaration. The whole array in the example is decodable based on the syntax used in the call to the decode method.
在下面的示例中,GroceryProduct结构是可自动解码的,因为与Codable协议的一致性包含在其声明中。 示例中的整个数组可以根据对decode方法的调用中使用的语法进行解码。
If the JSON array contains even one element that isn't a GroceryProduct instance, the decoding fails. This way, data isn't silently lost as a result of typos or a misunderstanding of the guarantees made by the provider of the JSON array.
如果JSON数组甚至包含一个不是GroceryProduct实例的元素,则解码失败。 这样,由于拼写错误或对JSON数组提供程序所做的保证的误解,数据不会无声地丢失。
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange",
"points": 100
}
]
""".data(using: .utf8)!
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let decoder = JSONDecoder()
let products = try? decoder.decode([GroceryProduct].self, from: json)
for item in products! {
print("\(item.name) -- \(item.points)")
if let des = item.description {
print(des)
}
}
}
}
2.Change Key Names
Names you use in your Swift code don't always match the names in JSON that refer to the same values. When working with the JSONEncoder and JSONDecoder classes in Swift, you can easily adopt conventional Swift names in your data types even when using JSON that requires the use of other names.
您在Swift代码中使用的名称并不总是与引用相同值的JSON中的名称匹配。 在Swift中使用JSONEncoder和JSONDecoder类时,即使使用需要使用其他名称的JSON,也可以轻松地在数据类型中采用传统的Swift名称。
To create a mapping between Swift names and JSON names, you use a nested enumeration named CodingKeys within the same type that adds conformance to Codable, Encodable, or Decodable.
要在Swift名称和JSON名称之间创建映射,可以使用名为CodingKeys的嵌套枚举,该枚举在同一类型中添加与Codable,Encodable或Decodable的一致性。
In the example below, see how the Swift property name points is mapped to and from the name "product_name" when the property is encoded and decoded.
在下面的示例中,查看在对属性进行编码和解码时,Swift属性名称点如何映射到名称“product_name”以及如何映射。
let json = """
[
{
"product_name": "Bananas",
"product_cost": 200,
"description": "A banana grown in Ecuador."
},
{
"product_name": "Oranges",
"product_cost": 100,
"description": "A juicy orange."
}
]
""".data(using: .utf8)!
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
private enum CodingKeys: String, CodingKey {
case name = "product_name"
case points = "product_cost"
case description
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let decoder = JSONDecoder()
let products = try? decoder.decode([GroceryProduct].self, from: json)
print("The following products are available:")
for product in products! {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
}
}
Although the name description is consistent between the two representations, you still include it in the CodingKeys enumeration because it's a value required by the GroceryProduct structure. Its enumeration case doesn't need an explicit raw value because its name is the same as the corresponding property name.
虽然两个表示之间的description变量是一致的,但您仍然将它包含在CodingKeys枚举中,因为它是GroceryProduct结构所需的值。 其枚举情况不需要显式原始值,因为其名称与相应的属性名称相同。
3.Access Nested Data
You can write an app that uses JSON from an external source or an existing local format. In either instance, you might find inconsistencies between the structure of the concepts you're modeling in your app and concepts modeled by the producer of the JSON. Sometimes, a logical bundle of data for your Swift program is spread out among several nested objects or arrays in the JSON you use. Bridge the structural gap by writing a decodable type that matches the structure of the JSON you're reading in. The decodable type serves as an intermediate type that's safe to decode. It serves as the data source in an initializer for the type that you'll use in the rest of your app.
您可以编写一个使用外部JSON或现有本地JSON的应用程序。 在任何一种情况下,您可能会发现在应用程序中你的模型结构与JSON中的模型结构之间存在不一致的地方。 有时,Swift程序的逻辑数据包分散在您使用的JSON的几个嵌套对象中或数组中。 通过编写与您正在读取的JSON结构相匹配的可解码类型来桥接结构间隙。可解码类型用作可安全解码的中间类型。 它作为初始化程序中的数据源,用于您将在应用程序接下来使用的类型。
With intermediate types, you can use the most natural types in your own code while maintaining compatibility with a variety of shapes of external JSON.
对于中间类型,您可以在自己的代码中使用最自然的类型,同时保持与各种形状的外部JSON的兼容性。
The example below introduces a type representing a grocery store and a list of the products it sells:
下面的示例介绍了代表杂货店的类型以及它销售的产品列表:
// An API might supply information about grocery stores using JSON that's structured as follows:
// API可能会使用JSON提供有关杂货店的信息,其结构如下:
let json = """
[
{
"name": "Home Town Market",
"aisles": [
{
"name": "Produce",
"shelves": [
{
"name": "Discount Produce",
"product": {
"name": "Banana",
"points": 200,
"description": "A banana that's perfectly ripe."
}
}
]
}
]
},
{
"name": "Big City Market",
"aisles": [
{
"name": "Sale Aisle",
"shelves": [
{
"name": "Seasonal Sale",
"product": {
"name": "Chestnuts",
"points": 700,
"description": "Chestnuts that were roasted over an open fire."
}
},
{
"name": "Last Season's Clearance",
"product": {
"name": "Pumpkin Seeds",
"points": 400,
"description": "Seeds harvested from a pumpkin."
}
}
]
}
]
}
]
""".data(using: .utf8)!
struct GroceryStore {
var name: String
var products: [Product]
struct Product: Codable {
var name: String
var points: Int
var description: String?
}
}
The JSON returned by the API contains more information than is needed to populate the corresponding Swift type. In particular, it has a structural incompatibility with the GroceryStore structure defined earlier: its products are nested inside aisles and shelves. Although the provider of the JSON likely needs the extra information, it might not be useful inside all of the apps that depend on it.
API返回的JSON包含的信息多于填充相应Swift类型所需的信息。 特别是,它与前面定义的GroceryStore结构具有结构不兼容性:它的产品嵌套在过道和货架内。 虽然JSON的提供者可能需要额外的信息,但它可能在所有依赖它的应用程序中都没有用。
To extract the data you need from the outer containers, you write a type that mirrors the shape of the source JSON and mark it as Decodable. Then, write an initializer on the type you'll use in the rest of your app that takes an instance of the type that mirrors the source JSON.
要从外部容器中提取所需的数据,可以编写一个反映源JSON形状的类型,并将其标记为Decodable。 然后,在您将在应用程序的其余部分中使用的类型上编写初始化程序,该类型采用镜像源JSON的类型的实例。
In the example below, the GroceryStoreService structure serves as an intermediary between the grocery JSON and the GroceryStore structure that is ideal for its intended use in an app:
在下面的示例中,GroceryStoreService结构充当杂货JSON和GroceryStore结构之间的中介,非常适合在应用程序中使用它:
struct GroceryStoreService: Decodable {
let name: String
let aisles: [Aisle]
struct Aisle: Decodable {
let name: String
let shelves: [Shelf]
struct Shelf: Decodable {
let name: String
let product: GroceryStore.Product
}
}
}
Because the GroceryStoreService structure matches the structure of the source JSON—including aisles and shelves—its conformance to the Decodable protocol is automatic when the protocol is included in the structure's list of inherited types. The GroceryStore structure's nested Product structure is reused in the Shelf structure because the data uses the same names and types.
因为GroceryStoreService结构与源JSON的结构(包括通道和架子)匹配 - 当协议包含在结构的继承类型列表中时,它与Decodable协议的一致性是自动的。 GroceryStore结构的嵌套Product结构在Shelf结构中重用,因为数据使用相同的名称和类型。
To complete the GroceryStoreService structure's role as an intermediate type, use an extension to the GroceryStore structure. The extension adds an initializer that takes a GroceryStoreService instance and removes the unnecessary nesting by looping through and discarding the aisles and shelves:
要完成GroceryStoreService结构作为中间类型的角色,请使用GroceryStore结构的扩展。 扩展添加了一个初始化程序,它接受GroceryStoreService实例并通过循环并丢弃过道和架子来删除不必要的嵌套:
extension GroceryStore {
init(from service: GroceryStoreService) {
name = service.name
products = []
for aisle in service.aisles {
for shelf in aisle.shelves {
products.append(shelf.product)
}
}
}
}
Using the relationships between types in the examples above, you can safely and succinctly read in JSON, pass it through the GroceryStoreService intermediate type, and use the resulting GroceryStore instances in your app:
使用上面示例中的类型之间的关系,您可以安全,简洁地读取JSON,将其传递给GroceryStoreService中间类型,并在您的应用中使用生成的GroceryStore实例:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let decoder = JSONDecoder()
let serviceStores = try! decoder.decode([GroceryStoreService].self, from: json)
let stores = serviceStores.map { GroceryStore(from: $0) }
for store in stores {
print("\(store.name) is selling:")
for product in store.products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}
}
}
}
4.Merge Data from Different Depths
合并来自不同深度的数据
Sometimes the data model used by a JSON file or API doesn't match the model you're using in an app. When that happens, you may need to merge or separate objects from the JSON when encoding and decoding. As a result, the encoding or decoding of a single instance involves reaching upward or downward in the JSON object's hierarchy.
有时,JSON文件或API使用的数据模型与您在应用程序中使用的模型不匹配。 发生这种情况时,您可能需要在编码和解码时将对象与JSON合并或分离。 因此,单个实例的编码或解码涉及在JSON对象的层次结构中向外层或向内层来获取。
The example below demonstrates a common occurrence of this style of data merging. It models a grocery store that keeps track of the name, price, and other details for each of the products it sells.
下面的示例演示了此类数据合并的常见情况。 它为杂货店建模,跟踪其销售的每种产品的名称,价格和其他详细信息。
let json = """
{
"Banana": {
"points": 200,
"description": "A banana grown in Ecuador."
},
"Orange": {
"points": 100
}
}
""".data(using: .utf8)!
Notice that the name of the product is also the name of the key that defines the rest of the details of the product. In this case, that the information for the "Banana" product is stored in an object nested under the product name itself. However, it's only by convention that it's clear that the name of the product comes from the object's identifying key.
请注意,产品的名称也是定义产品其余详细信息的键的名称。 在这种情况下,“Banana”产品的信息存储在嵌套在产品名称本身下的对象中。 但是,按照惯例,很明显产品的名称来自对象的识别键。
By contrast, another formulation of the JSON structure could have had a "product" key for each product and a "name" key to store each of the individual product names. That alternative formulation matches how you model the data in Swift, as seen in the example below:
相比之下,JSON结构的另一种表述可能是每个产品都有一个“产品”键,而一个“名称”键用于存储每个产品名称。 该替代公式与您在Swift中建模数据的方式相匹配,如下例所示:
struct GroceryStore {
struct Product {
let name: String
let points: Int
let description: String?
}
var products: [Product]
init(products: [Product] = []) {
self.products = products
}
}
The following extension to the GroceryStore structure makes it conform to the Encodable protocol, which brings the structure halfway to eventual conformance to the Codable protocol. Notably, it uses a nested structure, ProductKey, rather than the more typical enumeration with the same kind of conformance to the CodingKey protocol. A structure is needed to account for a possibly unlimited number of coding keys that might be used as names for instances of the Product structure.
GroceryStore结构的以下扩展使其符合Encodable协议,该协议使结构中途最终符合Codable协议。 值得注意的是,它使用嵌套结构ProductKey,而不是更典型的枚举,与CodingKey协议具有相同的一致性。 需要一种结构来考虑可能无限数量的编码密钥,这些编码密钥可以用作产品结构实例的名称。
extension GroceryStore: Encodable {
struct ProductKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let points = ProductKey(stringValue: "points")!
static let description = ProductKey(stringValue: "description")!
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ProductKey.self)
for product in products {
// Any product's `name` can be used as a key name.
// 任何产品的“名称”都可以用作键名。
let nameKey = ProductKey(stringValue: product.name)!
var productContainer = container.nestedContainer(keyedBy: ProductKey.self, forKey: nameKey)
// The rest of the keys use static names defined in `ProductKey`.
// 其余的键使用`ProductKey`中定义的静态名称。
try productContainer.encode(product.points, forKey: .points)
try productContainer.encode(product.description, forKey: .description)
}
}
}
With the conformance to the Encodable protocol in the example above, any GroceryStore instance can be encoded into JSON using a JSONEncoder instance:
通过上面示例中的Encodable协议的一致性,可以使用JSONEncoder实例将任何GroceryStore实例编码为JSON:
var encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let store = GroceryStore(products: [
.init(name: "Grapes", points: 230, description: "A mixture of red and green grapes."),
.init(name: "Lemons", points: 2300, description: "An extra sour lemon.")
])
print("The result of encoding a GroceryStore:")
let encodedStore = try encoder.encode(store)
print(String(data: encodedStore, encoding: .utf8)!)
print()
The second half of implementing conformance to the Codable protocol is decoding. The following extension completes the conformance for the GroceryStore structure. As part of decoding incoming JSON objects, the initializer loops over all of the keys of the first level of nesting in the object.
实现与Codable协议一致的后半部分是解码。 以下扩展完成了GroceryStore结构的一致性。 作为解码传入JSON对象的一部分,初始化器循环遍历对象中第一级嵌套的所有键。
extension GroceryStore: Decodable {
public init(from decoder: Decoder) throws {
var products = [Product]()
let container = try decoder.container(keyedBy: ProductKey.self)
for key in container.allKeys {
// Note how the `key` in the loop above is used immediately to access a nested container.
//注意如何立即使用上面循环中的`key`来访问嵌套容器。
let productContainer = try container.nestedContainer(keyedBy: ProductKey.self, forKey: key)
let points = try productContainer.decode(Int.self, forKey: .points)
let description = try productContainer.decodeIfPresent(String.self, forKey: .description)
// The key is used again here and completes the collapse of the nesting that existed in the JSON representation.
// 此处再次使用该键,并完成JSON表示中存在的嵌套的崩溃。
let product = Product(name: key.stringValue, points: points, description: description)
products.append(product)
}
self.init(products: products)
}
}
let decoder = JSONDecoder()
let decodedStore = try decoder.decode(GroceryStore.self, from: json)
print("The store is selling the following products:")
for product in decodedStore.products {
print("\t\(product.name) (\(product.points) points)")
if let description = product.description {
print("\t\t\(description)")
}
}