Swift 4では、ネイティブJSONエンコードとデコードのサポートが導入されました。Decodable
プロトコル。これにカスタム キーを使用するにはどうすればよいですか?
例えば、構造体があるとします
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
が、これをどのように使用すればよいかわかりません。
ベストアンサー1
コーディングキーを手動でカスタマイズする
あなたの例では、自動生成された適合性を取得していますCodable
すべてのプロパティも に準拠しているためですCodable
。この準拠により、プロパティ名に対応するキー タイプが自動的に作成され、単一のキー付きコンテナーへのエンコードや単一のキー付きコンテナーからのデコードに使用されます。
しかし、本当にこの自動生成された適合性の優れた特徴は、 " "enum
と呼ばれる型にネストされた を定義すると(またはこの名前で を使用すると)、CodingKeys
typealias
CodingKey
プロトコル – Swiftは自動的にこれキー タイプとして。これにより、プロパティをエンコード/デコードするキーを簡単にカスタマイズできます。
つまり、次のように言うことができます。
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
キーを使用してエンコード/デコードされるようになります"zip_code"
。
自動生成された正確なルールEncodable
/Decodable
適合性の詳細は進化論の提案(強調は筆者による):
CodingKey
の自動要件合成に加えてenums
、特定のタイプに対してEncodable
もDecodable
要件を自動的に合成できます。
Encodable
すべてプロパティがに準拠する型は、Encodable
自動的に生成されたベースString
の列挙型CodingKey
で、プロパティをケース名にマッピングします。同様に、Decodable
すべてプロパティがDecodable
(1)に該当するタイプ
CodingKey
enum
および手動で( という名前でCodingKeys
、直接、または を介して)提供する型。そのケースは、名前によって/プロパティtypealias
に 1 対 1 でマッピングされます。Encodable
Decodable
—それらのプロパティとキーを使用して、必要に応じて自動的に合成しinit(from:)
、encode(to:)
(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")
プロパティ名の自動snake_case
JSONキーcamelCase
Swift 4.1 では、zip
プロパティの名前を に変更するとzipCode
、 および のキーのエンコード/デコード戦略を利用してJSONEncoder
、とJSONDecoder
の間でコーディング キーを自動的に変換できます。camelCase
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設計ガイドライン、位置に応じて大文字または小文字に統一する必要があります。
たとえば、 という名前のプロパティ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
}
(これはあなたの特定の質問に厳密に答えているわけではありませんが、この Q&A の標準的な性質を考えると、含める価値があると思います)
カスタム自動JSONキーマッピング
Swift 4.1 では、 および のカスタム キー エンコード/デコード戦略を活用してJSONEncoder
、JSONDecoder
コーディング キーをマップするカスタム関数を提供できます。
指定した関数は[CodingKey]
、エンコード/デコードにおける現在のポイントのコーディング パスを表す を受け取ります (ほとんどの場合、最後の要素、つまり現在のキーのみを考慮する必要があります)。関数は、CodingKey
この配列の最後のキーを置き換える を返します。
たとえば、プロパティ名UpperCamelCase
の JSON キーは次のようになります。lowerCamelCase
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")