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:
Varieties conforming to
Encodable
whose properties are allEncodable
get an routinely generatedString
-backedCodingKey
enum mapping
properties to case names. Equally forDecodable
varieties whose
properties are allDecodable
Varieties falling into (1) — and kinds which manually present a
CodingKey
enum
(namedCodingKeys
, immediately, or by way of atypealias
) whose
circumstances map 1-to-1 toEncodable
/Decodable
properties by title — get
computerized synthesis ofinit(from:)
andencode(to:)
as acceptable,
utilizing these properties and keysVarieties 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")