如何在 Swift 4 的可解码协议中使用自定义键? [英] How do I use custom keys with Swift 4's Decodable protocol?

查看:26
本文介绍了如何在 Swift 4 的可解码协议中使用自定义键?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Swift 4 introduced support for native JSON encoding and decoding via the Decodable protocol. How do I use custom keys for this?

例如,假设我有一个结构

E.g., say I have a struct

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

我可以将其编码为 JSON.

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

我可以将其编码回一个对象.

I can encode this back to an object.

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

但是如果我有一个

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

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

How would I tell the decoder on Address that zip_code maps to zip? I believe you use the new CodingKey protocol, but I can't figure out how to use this.

推荐答案

手动自定义编码键

在您的示例中,您获得了对 Codable 因为您的所有属性也符合 Codable.这种一致性会自动创建一个与属性名称简单对应的密钥类型 - 然后使用它来编码到单个密钥容器/从单个密钥容器解码.

Manually customising coding keys

In your example, you're getting an auto-generated conformance to Codable as all your properties also conform to Codable. This conformance automatically creates a key type that simply corresponds to the property names – which is then used in order to encode to/decode from a single keyed container.

然而,这个自动生成的一致性的一个真正巧妙的特性是,如果你在你的类型中定义一个名为CodingKeys"的嵌套enum(或使用具有此名称的 typealias)符合 CodingKey 协议——Swift 将自动使用 this 作为密钥类型.因此,这允许您轻松自定义用于对您的属性进行编码/解码的密钥.

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.

所以这意味着你可以说:

So what this means is you can just say:

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<的原始值/code> 枚举将与案例名称相同).因此,现在将使用密钥 "zip_code"zip 属性进行编码/解码.

The enum case names need to match the property names, and the raw values of these cases need to match the keys that you're encoding to/decoding from (unless specified otherwise, the raw values of a String enumeration will the same as the case names). Therefore, the zip property will now be encoded/decoded using the key "zip_code".

自动生成的Encodable的确切规则/Decodable 一致性由 进化提议(重点是我的):

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

除了自动CodingKey需求合成之外enums, Encodable &Decodable 需求可以自动也为某些类型合成:

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

  1. 符合Encodable的类型,其属性都是Encodable,得到一个自动生成的String支持的CodingKey 枚举映射属性到案例名称.同样对于 Decodable 类型属性都是 Decodable

  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

属于 (1) - 的类型和手动提供 CodingKey enum(命名为 CodingKeys,直接,或通过 typealias)案例将一对一映射到 Encodable/Decodable 属性按名称 — 得到根据需要自动合成 init(from:)encode(to:),使用这些属性和键

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

既不属于 (1) 也不属于 (2) 的类型将必须在需要时提供自定义键类型并提供自己的 init(from:)encode(to:),视情况而定

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

<小时>

自动snake_casecamelCase 属性名称的JSON 键

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


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

然而,关于此策略需要注意的一件重要事情是,根据 Swift API 设计指南,应该统一大写或小写(取决于位置).

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

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

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 由编码器):

To fix this, you'll have to manually specify the coding key for that property to be string that the decoder expects, e.g someUrl in this case (which will still be transformed to some_url by the encoder):

struct S : Codable {

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

  var someURL: String
  var someOtherProperty: String
}

<小时>

(这并没有严格回答您的具体问题,但鉴于此问答的规范性,我觉得值得包含)

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

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,它将替换此数组中的最后一个键.

The function you provide takes a [CodingKey], which represents the coding path for the current point in encoding/decoding (in most cases, you'll only need to consider the last element; that is, the current key). The function returns a CodingKey that will replace the last key in this array.

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

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 关键策略进行编码:

You can now encode with the .convertToUpperCamelCase key strategy:

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 关键策略解码:

and decode with the .convertFromUpperCamelCase key strategy:

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

这篇关于如何在 Swift 4 的可解码协议中使用自定义键?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆