Demonstrates approaches for encoding and decoding different kinds of JSON in Swift.
演示在Swift中編碼和解碼不同類(lèi)型的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.
您從其他應(yīng)用程序讼昆,服務(wù)和文件發(fā)送或接收的JSON數(shù)據(jù)可以有許多不同的形狀和結(jié)構(gòu)幢码。 使用此示例中描述的技術(shù)來(lái)處理外部JSON數(shù)據(jù)與應(yīng)用程序模型類(lèi)型之間的差異惯吕。
This sample defines a simple data type, GroceryProduct, and demonstrates how to construct instances of that type from several different JSON formats.
此示例定義了一個(gè)簡(jiǎn)單的數(shù)據(jù)類(lèi)型GroceryProduct堪藐,并演示了如何從幾種不同的JSON格式構(gòu)造該類(lèi)型的實(shí)例榛做。
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的表達(dá)式系統(tǒng)來(lái)避免手動(dòng)循環(huán)相同結(jié)構(gòu)對(duì)象的集合序苏。 這個(gè)游樂(lè)場(chǎng)使用數(shù)組類(lèi)型作為值來(lái)查看如何使用結(jié)構(gòu)如下的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 鍵中的數(shù)據(jù)映射到你自定義類(lèi)型的屬性中绿饵,無(wú)論其名稱(chēng)如何。 例如垄懂,此游樂(lè)場(chǎng)顯示如何將下面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.
通過(guò)自定義映射骑晶,您可以將Swift API設(shè)計(jì)指南應(yīng)用于Swift模型中的屬性名稱(chēng),即使JSON鍵的名稱(chēng)不同也是如此草慧。
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結(jié)構(gòu)和數(shù)據(jù)桶蛔。 這個(gè)游樂(lè)場(chǎng)使用中間類(lèi)型來(lái)查看如何從JSON中提取雜貨產(chǎn)品,看起來(lái)像這樣跳過(guò)不需要的數(shù)據(jù)和結(jié)構(gòu):
[
{
"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:
通過(guò)編寫(xiě)來(lái)自Encodable和Decodable的協(xié)議要求的自定義實(shí)現(xiàn)漫谷,組合或分離來(lái)自JSON結(jié)構(gòu)的不同深度的數(shù)據(jù)仔雷。 這個(gè)游樂(lè)場(chǎng)展示了如何從JSON構(gòu)建一個(gè)GroceryProduct實(shí)例,如下所示:
{
"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.
當(dāng)您在Swift中控制數(shù)據(jù)類(lèi)型的結(jié)構(gòu)和用于編碼和解碼的JSON結(jié)構(gòu)一樣時(shí)舔示,請(qǐng)使用JSONEncoder和JSONDecoder類(lèi)為您生成的默認(rèn)格式碟婆。
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.
但是,當(dāng)代碼模型的數(shù)據(jù)遵循不應(yīng)更改的規(guī)范時(shí)惕稻,您仍應(yīng)根據(jù)Swift最佳實(shí)踐和約定編寫(xiě)數(shù)據(jù)類(lèi)型竖共。 此示例演示如何使用類(lèi)型與Codable協(xié)議的一致性作為數(shù)據(jù)的JSON表示與Swift中的相應(yīng)表示之間的轉(zhuǎn)換層。
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.
當(dāng)您使用的JSON包含同構(gòu)數(shù)組元素時(shí)俺祠,您可以在單個(gè)元素的類(lèi)型上添加Codable協(xié)議的一致性公给。 要解碼或編碼整個(gè)數(shù)組,請(qǐng)使用語(yǔ)法[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結(jié)構(gòu)是可自動(dòng)解碼的,因?yàn)榕cCodable協(xié)議的一致性包含在其聲明中宋梧。 示例中的整個(gè)數(shù)組可以根據(jù)對(duì)decode方法的調(diào)用中使用的語(yǔ)法進(jìn)行解碼匣沼。
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數(shù)組甚至包含一個(gè)不是GroceryProduct實(shí)例的元素,則解碼失敗捂龄。 這樣释涛,由于拼寫(xiě)錯(cuò)誤或?qū)SON數(shù)組提供程序所做的保證的誤解加叁,數(shù)據(jù)不會(huì)無(wú)聲地丟失。
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代碼中使用的名稱(chēng)并不總是與引用相同值的JSON中的名稱(chēng)匹配唇撬。 在Swift中使用JSONEncoder和JSONDecoder類(lèi)時(shí)它匕,即使使用需要使用其他名稱(chēng)的JSON,也可以輕松地在數(shù)據(jù)類(lèi)型中采用傳統(tǒng)的Swift名稱(chēng)窖认。
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名稱(chēng)和JSON名稱(chēng)之間創(chuàng)建映射豫柬,可以使用名為CodingKeys的嵌套枚舉,該枚舉在同一類(lèi)型中添加與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.
在下面的示例中,查看在對(duì)屬性進(jìn)行編碼和解碼時(shí)喝噪,Swift屬性名稱(chēng)點(diǎn)如何映射到名稱(chēng)“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.
雖然兩個(gè)表示之間的description變量是一致的,但您仍然將它包含在CodingKeys枚舉中酝惧,因?yàn)樗荊roceryProduct結(jié)構(gòu)所需的值榴鼎。 其枚舉情況不需要顯式原始值,因?yàn)槠涿Q(chēng)與相應(yīng)的屬性名稱(chēng)相同晚唇。
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.
您可以編寫(xiě)一個(gè)使用外部JSON或現(xiàn)有本地JSON的應(yīng)用程序巫财。 在任何一種情況下,您可能會(huì)發(fā)現(xiàn)在應(yīng)用程序中你的模型結(jié)構(gòu)與JSON中的模型結(jié)構(gòu)之間存在不一致的地方哩陕。 有時(shí)平项,Swift程序的邏輯數(shù)據(jù)包分散在您使用的JSON的幾個(gè)嵌套對(duì)象中或數(shù)組中。 通過(guò)編寫(xiě)與您正在讀取的JSON結(jié)構(gòu)相匹配的可解碼類(lèi)型來(lái)橋接結(jié)構(gòu)間隙萌踱】瘢可解碼類(lèi)型用作可安全解碼的中間類(lèi)型号阿。 它作為初始化程序中的數(shù)據(jù)源并鸵,用于您將在應(yīng)用程序接下來(lái)使用的類(lèi)型。
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.
對(duì)于中間類(lèi)型扔涧,您可以在自己的代碼中使用最自然的類(lèi)型园担,同時(shí)保持與各種形狀的外部JSON的兼容性。
The example below introduces a type representing a grocery store and a list of the products it sells:
下面的示例介紹了代表雜貨店的類(lèi)型以及它銷(xiāo)售的產(chǎn)品列表:
// An API might supply information about grocery stores using JSON that's structured as follows:
// API可能會(huì)使用JSON提供有關(guān)雜貨店的信息枯夜,其結(jié)構(gòu)如下:
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包含的信息多于填充相應(yīng)Swift類(lèi)型所需的信息弯汰。 特別是,它與前面定義的GroceryStore結(jié)構(gòu)具有結(jié)構(gòu)不兼容性:它的產(chǎn)品嵌套在過(guò)道和貨架內(nèi)湖雹。 雖然JSON的提供者可能需要額外的信息咏闪,但它可能在所有依賴(lài)它的應(yīng)用程序中都沒(méi)有用。
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.
要從外部容器中提取所需的數(shù)據(jù)摔吏,可以編寫(xiě)一個(gè)反映源JSON形狀的類(lèi)型鸽嫂,并將其標(biāo)記為Decodable纵装。 然后,在您將在應(yīng)用程序的其余部分中使用的類(lèi)型上編寫(xiě)初始化程序据某,該類(lèi)型采用鏡像源JSON的類(lèi)型的實(shí)例橡娄。
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結(jié)構(gòu)充當(dāng)雜貨JSON和GroceryStore結(jié)構(gòu)之間的中介癣籽,非常適合在應(yīng)用程序中使用它:
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.
因?yàn)镚roceryStoreService結(jié)構(gòu)與源JSON的結(jié)構(gòu)(包括通道和架子)匹配 - 當(dāng)協(xié)議包含在結(jié)構(gòu)的繼承類(lèi)型列表中時(shí)挽唉,它與Decodable協(xié)議的一致性是自動(dòng)的。 GroceryStore結(jié)構(gòu)的嵌套Product結(jié)構(gòu)在Shelf結(jié)構(gòu)中重用筷狼,因?yàn)閿?shù)據(jù)使用相同的名稱(chēng)和類(lèi)型瓶籽。
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結(jié)構(gòu)作為中間類(lèi)型的角色,請(qǐng)使用GroceryStore結(jié)構(gòu)的擴(kuò)展桑逝。 擴(kuò)展添加了一個(gè)初始化程序棘劣,它接受GroceryStoreService實(shí)例并通過(guò)循環(huán)并丟棄過(guò)道和架子來(lái)刪除不必要的嵌套:
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:
使用上面示例中的類(lèi)型之間的關(guān)系,您可以安全楞遏,簡(jiǎn)潔地讀取JSON茬暇,將其傳遞給GroceryStoreService中間類(lèi)型,并在您的應(yīng)用中使用生成的GroceryStore實(shí)例:
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
合并來(lái)自不同深度的數(shù)據(jù)
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.
有時(shí)寡喝,JSON文件或API使用的數(shù)據(jù)模型與您在應(yīng)用程序中使用的模型不匹配糙俗。 發(fā)生這種情況時(shí),您可能需要在編碼和解碼時(shí)將對(duì)象與JSON合并或分離预鬓。 因此巧骚,單個(gè)實(shí)例的編碼或解碼涉及在JSON對(duì)象的層次結(jié)構(gòu)中向外層或向內(nèi)層來(lái)獲取。
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.
下面的示例演示了此類(lèi)數(shù)據(jù)合并的常見(jiàn)情況格二。 它為雜貨店建模劈彪,跟蹤其銷(xiāo)售的每種產(chǎn)品的名稱(chēng),價(jià)格和其他詳細(xì)信息顶猜。
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.
請(qǐng)注意沧奴,產(chǎn)品的名稱(chēng)也是定義產(chǎn)品其余詳細(xì)信息的鍵的名稱(chēng)。 在這種情況下长窄,“Banana”產(chǎn)品的信息存儲(chǔ)在嵌套在產(chǎn)品名稱(chēng)本身下的對(duì)象中滔吠。 但是,按照慣例挠日,很明顯產(chǎn)品的名稱(chēng)來(lái)自對(duì)象的識(shí)別鍵疮绷。
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結(jié)構(gòu)的另一種表述可能是每個(gè)產(chǎn)品都有一個(gè)“產(chǎn)品”鍵嚣潜,而一個(gè)“名稱(chēng)”鍵用于存儲(chǔ)每個(gè)產(chǎn)品名稱(chēng)冬骚。 該替代公式與您在Swift中建模數(shù)據(jù)的方式相匹配,如下例所示:
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結(jié)構(gòu)的以下擴(kuò)展使其符合Encodable協(xié)議,該協(xié)議使結(jié)構(gòu)中途最終符合Codable協(xié)議只冻。 值得注意的是夜涕,它使用嵌套結(jié)構(gòu)ProductKey,而不是更典型的枚舉属愤,與CodingKey協(xié)議具有相同的一致性女器。 需要一種結(jié)構(gòu)來(lái)考慮可能無(wú)限數(shù)量的編碼密鑰,這些編碼密鑰可以用作產(chǎn)品結(jié)構(gòu)實(shí)例的名稱(chēng)住诸。
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.
// 任何產(chǎn)品的“名稱(chēng)”都可以用作鍵名驾胆。
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`中定義的靜態(tài)名稱(chēng)。
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:
通過(guò)上面示例中的Encodable協(xié)議的一致性贱呐,可以使用JSONEncoder實(shí)例將任何GroceryStore實(shí)例編碼為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.
實(shí)現(xiàn)與Codable協(xié)議一致的后半部分是解碼丧诺。 以下擴(kuò)展完成了GroceryStore結(jié)構(gòu)的一致性。 作為解碼傳入JSON對(duì)象的一部分奄薇,初始化器循環(huán)遍歷對(duì)象中第一級(jí)嵌套的所有鍵驳阎。
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.
//注意如何立即使用上面循環(huán)中的`key`來(lái)訪問(wèn)嵌套容器。
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)")
}
}