SWIFT 4通过Decodable协议引入了对原生JSON编码和解码的支持.如何使用自定义密钥进行此操作?

例如,假设我有一个 struct

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

I can encode this to 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)

But If I had a json object that was

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

我怎么告诉Address上的解码器zip_code映射到zip?我相信你用的是新的CodingKey协议,但我想不出怎么用.

推荐答案

手动自定义编码键

在您的示例中,您的所有属性都符合Codable,因此自动生成的一致性为Codable.这种一致性会自动创建一个只与属性名相对应的密钥类型,然后使用该类型对单个密钥容器进行编码/解码.

However one really neat feature of this auto-generated conformance is that if you define a nested enum in your type called "CodingKeys" (or use a typealias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.

这意味着你可以说:

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

枚举 case 名称需要与属性名称匹配,这些 case 的原始值需要与您编码/解码的密钥匹配(除非另有规定,否则String枚举的原始值将与 case 名称相同).因此,现在将使用密钥"zip_code"zip属性进行编码/解码.

The exact rules for the auto-generated Encodable/Decodable conformance are detailed by the evolution proposal (emphasis mine):

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. 如果需要,既不属于(1)也不属于(2)的类型必须提供自定义键类型,并提供它们自己的init(from:)encode(to:)(视何者适用而定)

编码示例:

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")

Automatic snake_case JSON keys for camelCase property names

In Swift 4.1, if you rename your zip property to zipCode, you can take advantage of the key encoding/decoding strategies on JSONEncoder and JSONDecoder in order to automatically convert coding keys between camelCase and snake_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()
  encoder.keyEncodingStrategy = .convertToSnakeCase
  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()
  decoder.keyDecodingStrategy = .convertFromSnakeCase
  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")

One important thing to note about this strategy however is that it won't be able to round-trip some property names with acronyms or initialisms which, according to the Swift API design guidelines, should be uniformly upper or lower case (depending on the position).

For example, a property named someURL will be encoded with the key some_url, but on decoding, this will be transformed to someUrl.

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

struct S : Codable {

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

  var someURL: String
  var someOtherProperty: String
}

(这并没有严格地回答您的具体问题,但考虑到本问答的规范性,我觉得它值得包括在内)

自定义自动JSON密钥映射

In Swift 4.1, you can take advantage of the custom key encoding/decoding strategies on JSONEncoder and JSONDecoder, allowing you to provide a custom function to map coding keys.

你所提供的函数取一个[CodingKey],它代表编码/解码中当前点的编码路径(在大多数情况下,你只需要考虑最后一个元素,即,当前密钥).该函数返回一个CodingKey,它将替换该数组中的最后一个键.

For example, UpperCamelCase JSON keys for lowerCamelCase property names:

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相关问答推荐

如何在使用GO时检测JSON中不需要的字段?

如何使用Aeson解码带有Unicode字符的JSON文件?

在Golang中从 struct 手动创建JSON对象

当console.log返回TRUE值时,解析的JSON中的字段未定义

Vegalite中分组条形图中的偏移量问题

JQ:获取该值的较短语法是什么

在 PowerShell 中通过 aws cli 创建 cloudwatch alert 时出现字符串解析错误

Delphi 11.3无法从变体创建/添加JSON

Golang gin接收json数据和图片

如何在 Flutter 中遍历嵌套的动态 JSON 文件

如何将从嵌套 Select 返回的空值转换为空数组?

如何为包含一些固定值并可能具有其他附加值的数组字符串创建数组 json 架构

JSON 模式实际用于什么目的?

C# 合并 2 个几乎相同的 JSON 对象

Android JSON 库的性能和可用性比较

jQuery fullcalendar 发送自定义参数并使用 JSON 刷新日历

错误未判断调用put(K, V)作为原始类型java.util.HashMap的成员

在 HTML 数据属性上添加 JSON 是不是很糟糕?

如何转换为 D3 的 JSON 格式?

如何将 mysqli 结果转换为 JSON?