HomeiOS Developmentjson - How do I take advantage of customized keys with Swift...

json – How do I take advantage of customized keys with Swift 4’s Decodable protocol?


Manually customising coding keys

In your instance, you are getting an auto-generated conformance to Codable as all of your properties additionally conform to Codable. This conformance routinely creates a key kind that merely corresponds to the property names – which is then used as a way to encode to/decode from a single keyed container.

Nonetheless one actually neat function of this auto-generated conformance is that when you outline a nested enum in your kind known as “CodingKeys” (or use a typealias with this title) that conforms to the CodingKey protocol – Swift will routinely use this as the important thing kind. This subsequently lets you simply customise the keys that your properties are encoded/decoded with.

So what this implies is you possibly can simply say:

struct Handle : Codable {

    var road: String
    var zip: String
    var metropolis: String
    var state: String

    personal enum CodingKeys : String, CodingKey {
        case road, zip = "zip_code", metropolis, state
    }
}

The enum case names must match the property names, and the uncooked values of those circumstances must match the keys that you simply’re encoding to/decoding from (except specified in any other case, the uncooked values of a String enumeration will the identical because the case names). Subsequently, the zip property will now be encoded/decoded utilizing the important thing "zip_code".

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

Along with computerized CodingKey requirement synthesis for
enums, Encodable & Decodable necessities might be routinely
synthesized for sure varieties as effectively:

  1. Varieties conforming to Encodable whose properties are all Encodable get an routinely generated String-backed CodingKey enum mapping
    properties to case names. Equally for Decodable varieties whose
    properties are all Decodable

  2. Varieties falling into (1) — and kinds which manually present a CodingKey enum (named CodingKeys, immediately, or by way of a typealias) whose
    circumstances map 1-to-1 to Encodable/Decodable properties by title
    — get
    computerized synthesis of init(from:) and encode(to:) as acceptable,
    utilizing these properties and keys

  3. Varieties which fall into neither (1) nor (2) should present a customized key kind if wanted and supply their very own init(from:) and
    encode(to:), as acceptable

Instance encoding:

import Basis

let deal with = Handle(road: "Apple Bay Avenue", zip: "94608",
                      metropolis: "Emeryville", state: "California")

do {
    let encoded = strive JSONEncoder().encode(deal with)
    print(String(decoding: encoded, as: UTF8.self))
} catch {
    print(error)
}
//{"state":"California","road":"Apple Bay Avenue","zip_code":"94608","metropolis":"Emeryville"}

Instance decoding:

// utilizing the """ multi-line string literal right here, as launched in SE-0168,
// to keep away from escaping the citation marks
let jsonString = """
{"state":"California","road":"Apple Bay Avenue","zip_code":"94608","metropolis":"Emeryville"}
"""

do {
    let decoded = strive JSONDecoder().decode(Handle.self, from: Knowledge(jsonString.utf8))
    print(decoded)
} catch {
    print(error)
}

// Handle(road: "Apple Bay Avenue", zip: "94608",
// metropolis: "Emeryville", state: "California")

Computerized snake_case JSON keys for camelCase property names

In Swift 4.1, when you rename your zip property to zipCode, you possibly can make the most of the important thing encoding/decoding methods on JSONEncoder and JSONDecoder as a way to routinely convert coding keys between camelCase and snake_case.

Instance encoding:

import Basis

struct Handle : Codable {
  var road: String
  var zipCode: String
  var metropolis: String
  var state: String
}

let deal with = Handle(road: "Apple Bay Avenue", zipCode: "94608",
                      metropolis: "Emeryville", state: "California")

do {
  let encoder = JSONEncoder()
  encoder.keyEncodingStrategy = .convertToSnakeCase
  let encoded = strive encoder.encode(deal with)
  print(String(decoding: encoded, as: UTF8.self))
} catch {
  print(error)
}
//{"state":"California","road":"Apple Bay Avenue","zip_code":"94608","metropolis":"Emeryville"}

Instance decoding:

let jsonString = """
{"state":"California","road":"Apple Bay Avenue","zip_code":"94608","metropolis":"Emeryville"}
"""

do {
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromSnakeCase
  let decoded = strive decoder.decode(Handle.self, from: Knowledge(jsonString.utf8))
  print(decoded)
} catch {
  print(error)
}

// Handle(road: "Apple Bay Avenue", zipCode: "94608",
// metropolis: "Emeryville", state: "California")

One vital factor to notice about this technique nevertheless is that it will not be capable to round-trip some property names with acronyms or initialisms which, in line with the Swift API design pointers, ought to be uniformly higher or decrease case (relying on the place).

For instance, a property named someURL can be encoded with the important thing some_url, however on decoding, this can be reworked to someUrl.

To repair this, you will should manually specify the coding key for that property to be string that the decoder expects, e.g someUrl on this case (which is able to nonetheless be reworked to some_url by the encoder):

struct S : Codable {

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

  var someURL: String
  var someOtherProperty: String
}

(This does not strictly reply your particular query, however given the canonical nature of this Q&A, I really feel it is price together with)

Customized computerized JSON key mapping

In Swift 4.1, you possibly can make the most of the customized key encoding/decoding methods on JSONEncoder and JSONDecoder, permitting you to offer a customized perform to map coding keys.

The perform you present takes a [CodingKey], which represents the coding path for the present level in encoding/decoding (typically, you will solely want to think about the final component; that’s, the present key). The perform returns a CodingKey that may substitute the final key on this array.

For instance, UpperCamelCase JSON keys for lowerCamelCase property names:

import Basis

// wrapper to permit 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 .customized { codingKeys in

      var key = AnyCodingKey(codingKeys.final!)

      // 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 .customized { codingKeys in

      var key = AnyCodingKey(codingKeys.final!)

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

Now you can encode with the .convertToUpperCamelCase key technique:

let deal with = Handle(road: "Apple Bay Avenue", zipCode: "94608",
                      metropolis: "Emeryville", state: "California")

do {
  let encoder = JSONEncoder()
  encoder.keyEncodingStrategy = .convertToUpperCamelCase
  let encoded = strive encoder.encode(deal with)
  print(String(decoding: encoded, as: UTF8.self))
} catch {
  print(error)
}
//{"Avenue":"Apple Bay Avenue","Metropolis":"Emeryville","State":"California","ZipCode":"94608"}

and decode with the .convertFromUpperCamelCase key technique:

let jsonString = """
{"Avenue":"Apple Bay Avenue","Metropolis":"Emeryville","State":"California","ZipCode":"94608"}
"""

do {
  let decoder = JSONDecoder()
  decoder.keyDecodingStrategy = .convertFromUpperCamelCase
  let decoded = strive decoder.decode(Handle.self, from: Knowledge(jsonString.utf8))
  print(decoded)
} catch {
  print(error)
}

// Handle(road: "Apple Bay Avenue", zipCode: "94608",
// metropolis: "Emeryville", state: "California")

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments