json - 如何通过 Swift 4 的 Decodable 协议(protocol)使用自定义键?

标签 json swift swift4 codable

Swift 4 通过 Decodable 引入了对 native JSON 编码和解码的支持协议(protocol)。我如何使用自定义键来实现此目的?

例如,假设我有一个结构

struct Address:Codable {
    var street:String
    var zip:String
    var city:String
    var state:String
}

我可以将其编码为 JSON。

let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")

if let encoded = try? encoder.encode(address) {
    if let json = String(data: encoded, encoding: .utf8) {
        // Print JSON String
        print(json)

        // JSON string is 
           { "state":"California", 
             "street":"Apple Bay Street", 
             "zip":"94608", 
             "city":"Emeryville" 
           }
    }
}

我可以将其编码回对象。

    let newAddress: Address = try decoder.decode(Address.self, from: encoded)

但是如果我有一个 json 对象

{ 
   "state":"California", 
   "street":"Apple Bay Street", 
   "zip_code":"94608", 
   "city":"Emeryville" 
}

我如何告诉 Address 上的解码器 zip_code 映射到 zip?我相信您使用了新的 CodingKey 协议(protocol),但我不知道如何使用它。

最佳答案

手动自定义编码键

在您的示例中,您将获得自动生成的 Codable 一致性因为您的所有属性也符合 Codable。这种一致性会自动创建一个与属性名称相对应的键类型,然后使用该键类型对单个键控容器进行编码或解码。

然而,这种自动生成的一致性的一个真正巧妙功能是,如果您在名为“CodingKeys”的类型中定义一个嵌套enum (或使用具有此名称的 typealias),符合 CodingKey协议(protocol) – Swift 将自动使用 this 作为键类型。因此,这使您可以轻松自定义对属性进行编码/解码的 key 。

所以这意味着你可以说:

struct Address : Codable {

    var street: String
    var zip: String
    var city: String
    var state: String

    private enum CodingKeys : String, CodingKey {
        case street, zip = "zip_code", city, state
    }
}

枚举案例名称需要与属性名称匹配,并且这些案例的原始值需要与您要编码/解码的键匹配(除非另有说明,String<的原始值 枚举将与案例名称相同)。因此,现在将使用键“zip_code”对 zip 属性进行编码/解码。

自动生成 Encodable 的确切规则/Decodable一致性详细信息为the evolution proposal (强调我的):

In addition to automatic CodingKey requirement synthesis for enums, Encodable & Decodable requirements can be automatically synthesized for certain types as well:

  1. Types conforming to Encodable whose properties are all Encodable get an automatically generated String-backed CodingKey enum mapping properties to case names. Similarly for Decodable types whose properties are all Decodable

  2. Types falling into (1) — and types which manually provide a CodingKey enum (named CodingKeys, directly, or via a typealias) whose cases map 1-to-1 to Encodable/Decodable properties by name — get automatic synthesis of init(from:) and encode(to:) as appropriate, using those properties and keys

  3. Types which fall into neither (1) nor (2) will have to provide a custom key type if needed and provide their own init(from:) and encode(to:), as appropriate

编码示例:

import Foundation

let address = Address(street: "Apple Bay Street", zip: "94608",
                      city: "Emeryville", state: "California")

do {
    let encoded = try JSONEncoder().encode(address)
    print(String(decoding: encoded, as: UTF8.self))
} catch {
    print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}

解码示例:

// using the """ multi-line string literal here, as introduced in SE-0168,
// to avoid escaping the quotation marks
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""

do {
    let decoded = try JSONDecoder().decode(Address.self, from: Data(jsonString.utf8))
    print(decoded)
} catch {
    print(error)
}

// Address(street: "Apple Bay Street", zip: "94608",
// city: "Emeryville", state: "California")
<小时/>

camelCase 属性名称的自动 snake_case JSON 键

在 Swift 4.1 中,如果将 zip 属性重命名为 zipCode,则可以利用 JSONEncoder 上的关键编码/解码策略和 JSONDecoder 以便在 camelCasesnake_case 之间自动转换编码键。

编码示例:

import Foundation

struct Address : Codable {
  var street: String
  var zipCode: String
  var city: String
  var state: String
}

let address = Address(street: "Apple Bay Street", zipCode: "94608",
                      city: "Emeryville", state: "California")

do {
  let encoder = JSONEncoder()
  <b>encoder.keyEncodingStrategy = .convertToSnakeCase</b>
  let encoded = try encoder.encode(address)
  print(String(decoding: encoded, as: UTF8.self))
} catch {
  print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}

解码示例:

let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""

do {
  let decoder = JSONDecoder()
  <b>decoder.keyDecodingStrategy = .convertFromSnakeCase</b>
  let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
  print(decoded)
} catch {
  print(error)
}

// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")

然而,关于此策略需要注意的一件重要事情是,根据 Swift API design guidelines,它将无法使用首字母缩略词或首字母缩写来往返某些属性名称。 ,应统一大写或小写(取决于位置)。

例如,名为 someURL 的属性将使用键 some_url 进行编码,但在解码时,它将转换为 someUrl

要解决此问题,您必须手动指定该属性的编码键为解码器期望的字符串,例如本例中的 someUrl (仍将转换为 some_url 由编码器生成):

struct S : Codable {

  private enum CodingKeys : String, CodingKey {
    case someURL = "someUrl", someOtherProperty
  }

  var someURL: String
  var someOtherProperty: String
}
<小时/>

(这并不能严格回答您的具体问题,但考虑到此问答的规范性质,我认为值得包含)

自定义自动 JSON 键映射

在 Swift 4.1 中,您可以利用 JSONEncoderJSONDecoder 上的自定义键编码/解码策略,允许您提供自定义函数来映射编码键。

您提供的函数采用[CodingKey],它表示编码/解码中当前点的编码路径(在大多数情况下,您只需要考虑最后一个元素;即是,当前 key )。该函数返回一个CodingKey,它将替换该数组中的最后一个键。

例如,UpperCamelCase 用于 lowerCamelCase 属性名称的 JSON 键:

import Foundation

// wrapper to allow us to substitute our mapped string keys.
struct AnyCodingKey : CodingKey {

  var stringValue: String
  var intValue: Int?

  init(_ base: CodingKey) {
    self.init(stringValue: base.stringValue, intValue: base.intValue)
  }

  init(stringValue: String) {
    self.stringValue = stringValue
  }

  init(intValue: Int) {
    self.stringValue = "\(intValue)"
    self.intValue = intValue
  }

  init(stringValue: String, intValue: Int?) {
    self.stringValue = stringValue
    self.intValue = intValue
  }
}

extension JSONEncoder.KeyEncodingStrategy {

  static var convertToUpperCamelCase: JSONEncoder.KeyEncodingStrategy {
    return .custom { codingKeys in

      var key = AnyCodingKey(codingKeys.last!)

      // uppercase first letter
      if let firstChar = key.stringValue.first {
        let i = key.stringValue.startIndex
        key.stringValue.replaceSubrange(
          i ... i, with: String(firstChar).uppercased()
        )
      }
      return key
    }
  }
}

extension JSONDecoder.KeyDecodingStrategy {

  static var convertFromUpperCamelCase: JSONDecoder.KeyDecodingStrategy {
    return .custom { codingKeys in

      var key = AnyCodingKey(codingKeys.last!)

      // lowercase first letter
      if let firstChar = key.stringValue.first {
        let i = key.stringValue.startIndex
        key.stringValue.replaceSubrange(
          i ... i, with: String(firstChar).lowercased()
        )
      }
      return key
    }
  }
}

您现在可以使用 .convertToUpperCamelCase 键策略进行编码:

let address = Address(street: "Apple Bay Street", zipCode: "94608",
                      city: "Emeryville", state: "California")

do {
  let encoder = JSONEncoder()
  encoder.keyEncodingStrategy = .convertToUpperCamelCase
  let encoded = try encoder.encode(address)
  print(String(decoding: encoded, as: UTF8.self))
} catch {
  print(error)
}
//{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}

并使用 .convertFromUpperCamelCase 键策略进行解码:

let jsonString = """
{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}
"""

do {
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromUpperCamelCase
  let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
  print(decoded)
} catch {
  print(error)
}

// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")

关于json - 如何通过 Swift 4 的 Decodable 协议(protocol)使用自定义键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54339874/

相关文章:

json - "login"和 "sign up"Web 服务未使用 alamofire 快速提供所需的 json 输出

ios - 如何给 SwiftUI arc 添加阴影?

swift - 在 Swift 4 上以编程方式设置按钮

swift - 过滤数据库子项并将每个名称作为字符串返回

swift - 列表不符合 Encodable

python csv writer没有正确处理转义 '|'

python - 如何在 python 中读取 JSON 文件时修复 UnicodeDecodeError

PHP MySQL 连接表作为 JSON

javascript - 解析google和twitter的json数据差异

swift - 覆盖属性观察者