Skip to content

Codable

ShenYj edited this page Mar 11, 2021 · 3 revisions

Codable

Codable协议在Swift4.0开始被引入,目标是取代现有的NSCoding协议,它对结构体,枚举和类都支持,能够把JSON这种弱类型数据转换成代码中使用的强类型数据

一、基本使用

import Foundation

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?
}

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
  • 非可选项的属性一定要保持一致, 否则就会decode失败, 返回nil
  • 模型中的属性相对于json中的key只多不少, 多出的属性
    • 如果是可选项, decode成功, 只不过是缺失的属性为nil
    • 如果是必选项, json中不存在时, 整个json decode失败, 返回nil

二、简单的映射

import Foundation

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
    }
}

let decoder = JSONDecoder()
let products = try? decoder.decode([GroceryProduct].self, from: json)

products?.forEach { print("name: \($0.name) points: \($0.points) description: \($0.description)") }
  • CodingKeys必须是嵌套在声明的struct中, 而且这个枚举名称固定格式
    • 比如我自定义个名字CodingKeyss, 就会decoding失败, 返回nil
  • CodingKeys必须遵守CodingKey协议
    • 因为键都是String类型,所以需要在CodingKeys上声明为String enum CodingKeys: String, CodingKey
  • 即使不打算重新命名所有的键也要在CodingKeys中列出所有的键
    • 如果CodingKeys中缺少某个模型属性, 编译器直接报错Type 'GroceryProduct' does not conform to protocol 'Decodable'

三、嵌套数据

  • 准备数据

    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 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
                  
                  struct GroceryStore {
                      var name: String
                      var products: [Product]
      
                      struct Product: Codable {
                          var name: String
                          var points: Int
                          var description: String?
                      }
                  }
              }
          }
      }
      
      for (index, store) in serviceStores.enumerated() {
          print("\(index).\(store.name) is selling:")
          for product in store.aisles {
              print("\t\(product.name) (\(product.shelves) shelves)")
              print("\t\t\(product.name)")
          }
      }
    • 苹果示例代码

      struct GroceryStore {
          var name: String
          var products: [Product]
          
          struct Product: Codable {
              var name: String
              var points: Int
              var description: String?
          }
      }
      
      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
              }
          }
      }
      
      extension GroceryStore {
          init(from service: GroceryStoreService) {
              name = service.name
              products = []
              
              for aisle in service.aisles {
                  for shelf in aisle.shelves {
                      products.append(shelf.product)
                  }
              }
          }
      }
      
      
      let decoder = JSONDecoder()
      let serviceStores = try decoder.decode([GroceryStoreService].self, from: json)
      let stores = serviceStores.map { GroceryStore(from: $0) }
      
      for (index, store) in stores.enumerated() {
          print("\(index).\(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)")
              }
          }
      }
  • 第一段是按照嵌套结构转成模型

  • 第二段是摘取有效部分组成一个模型(苹果示例代码) 区别

四、特殊/复杂json的处理

  • 准备数据

    let json = """ { "Banana": { "points": 200, "description": "A banana grown in Ecuador." }, "Orange": { "points": 100 } } """.data(using: .utf8)!

这段数据中外层是个字典, 包含了多种水果, 内部同样是个字典 常规思路, 我们可能更需要一个集合, 元素是字典(水果), 然后就可以这样定义模型类

struct Product {
    let name: String
    let points: Int
    let description: String?
}
  • 示例代码

    struct GroceryStore {
    
        struct Product {
            let name: String
            let points: Int
            let description: String?
        }
    
        var products: [Product]
    
        init(products: [Product] = []) {
            self.products = products
        }
    }
    
    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`.
                try productContainer.encode(product.points, forKey: .points)
                try productContainer.encode(product.description, forKey: .description)
            }
        }
    }
    
    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.
                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.
                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:")
    decodedStore.products.forEach{ print("\t name:\($0.name) - points:\($0.points) - description:\($0.description ?? "null")") }

Getting Started

Social

Clone this wiki locally