Swift 4では、 Decodable
プロトコルを介したネイティブJSONエンコードおよびデコードのサポートが導入されました。これにカスタムキーを使用するにはどうすればよいですか?
たとえば、構造体があるとします
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
プロトコルを使用していると思いますが、これを使用する方法がわかりません。
この例では、 Codable
に自動生成された適合性を取得しています。すべてのプロパティもCodable
に適合しています。この適合により、プロパティ名に単純に対応するキータイプが自動的に作成されます。このキータイプは、単一のキー付きコンテナからエンコード/デコードするために使用されます。
ただし、この自動生成された適合性の1つのreallyすてきな機能は、「enum
」と呼ばれるタイプでネストされたCodingKeys
を定義する場合(またはこの名前でtypealias
を使用する場合) CodingKey
プロトコルに準拠– Swiftは、キータイプとしてthisを自動的に使用します。したがって、これにより、プロパティがエンコード/デコードされるキーを簡単にカスタマイズできます。
これが意味することは、あなたが言うことができるということです:
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
- backedCodingKey
enumを取得します。同様に、プロパティがすべて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")
camelCase
プロパティ名の自動snake_case
JSONキーSwift 4.1では、Zip
プロパティの名前をzipCode
に変更すると、JSONEncoder
とsnake_case
の間でコーディングキーを自動的に変換するために、JSONDecoder
およびcamelCase
のキーエンコード/デコード戦略を利用できます。
エンコードの例:
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")
ただし、この戦略について注意すべき重要なことの1つは、 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の標準的な性質を考えると、含める価値があると思います)
Swift 4.1では、JSONEncoder
およびJSONDecoder
のカスタムキーエンコード/デコード戦略を利用して、コーディングキーをマップするカスタム関数を提供できます。
指定する関数は[CodingKey]
を取ります。これは、エンコード/デコードの現在のポイントのコーディングパスを表します(ほとんどの場合、最後の要素、つまり現在のキーのみを考慮する必要があります)。この関数は、この配列の最後のキーを置き換えるCodingKey
を返します。
たとえば、UpperCamelCase
プロパティ名のlowerCamelCase
JSONキー:
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")
Swift 4.2では、ニーズに応じて、モデルオブジェクトのカスタムプロパティ名をJSONキーに一致させるために、次の3つの戦略のいずれかを使用できます。
次の実装でCodable
(Decodable
およびEncodable
プロトコル)に適合する構造体を宣言する場合...
struct Address: Codable {
var street: String
var Zip: String
var city: String
var state: String
}
...コンパイラは、CodingKey
プロトコルに準拠するネストされた列挙を自動的に生成します。
struct Address: Codable {
var street: String
var Zip: String
var city: String
var state: String
// compiler generated
private enum CodingKeys: String, CodingKey {
case street
case Zip
case city
case state
}
}
したがって、シリアル化されたデータ形式で使用されるキーがデータ型のプロパティ名と一致しない場合、この列挙型を手動で実装し、必要な場合に適切なrawValue
を設定できます。
次の例は、その方法を示しています。
import Foundation
struct Address: Codable {
var street: String
var Zip: String
var city: String
var state: String
private enum CodingKeys: String, CodingKey {
case street
case Zip = "Zip_code"
case city
case state
}
}
エンコード(Zip
プロパティを「Zip_code」JSONキーに置き換え):
let address = Address(street: "Apple Bay Street", Zip: "94608", city: "Emeryville", state: "California")
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"state":"California","street":"Apple Bay Street","Zip_code":"94608","city":"Emeryville"}
*/
デコード(「Zip_code」JSONキーをZip
プロパティに置き換え):
let jsonString = """
{"state":"California","street":"Apple Bay Street","Zip_code":"94608","city":"Emeryville"}
"""
let decoder = JSONDecoder()
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", Zip: "94608", city: "Emeryville", state: "California")
*/
JSONにスネークケースキーがあり、モデルオブジェクトのキャメルケースプロパティに変換する場合、JSONEncoder
の keyEncodingStrategy
およびJSONDecoder
の keyDecodingStrategy
プロパティを.convertToSnakeCase
に設定できます。 。
次の例は、その方法を示しています。
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
エンコード(キャメルケースプロパティをスネークケースJSONキーに変換):
let address = Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"state":"California","street":"Apple Bay Street","Zip_code":"94608","city_name":"Emeryville"}
*/
デコード(スネークケースのJSONキーをキャメルケースのプロパティに変換):
let jsonString = """
{"state":"California","street":"Apple Bay Street","Zip_code":"94608","city_name":"Emeryville"}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")
*/
必要に応じて、JSONEncoder
およびJSONDecoder
を使用すると、カスタム戦略を設定して、 JSONEncoder.KeyEncodingStrategy.custom(_:)
および JSONDecoder.KeyDecodingStrategy.custom(_:)
を使用してコーディングキーをマッピングできます。
次の例は、それらの実装方法を示しています。
import Foundation
struct Address: Codable {
var street: String
var Zip: String
var city: String
var state: String
}
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
エンコード(小文字の最初の文字のプロパティを大文字の最初の文字のJSONキーに変換):
let address = Address(street: "Apple Bay Street", Zip: "94608", city: "Emeryville", state: "California")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .custom({ (keys) -> CodingKey in
let lastKey = keys.last!
guard lastKey.intValue == nil else { return lastKey }
let stringValue = lastKey.stringValue.prefix(1).uppercased() + lastKey.stringValue.dropFirst()
return AnyKey(stringValue: stringValue)!
})
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"Zip":"94608","Street":"Apple Bay Street","City":"Emeryville","State":"California"}
*/
デコード(大文字の最初の文字のJSONキーを小文字の最初の文字のプロパティに変換):
let jsonString = """
{"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in
let lastKey = keys.last!
guard lastKey.intValue == nil else { return lastKey }
let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst()
return AnyKey(stringValue: stringValue)!
})
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", Zip: "94608", city: "Emeryville", state: "California")
*/
ソース:
私がやったことは、データ型に関してJSONから取得するのと同じように、独自の構造を作成することです。
ちょうどこのような:
struct Track {
let id : Int
let contributingArtistNames:String
let name : String
let albumName :String
let copyrightP:String
let copyrightC:String
let playlistCount:Int
let trackPopularity:Int
let playlistFollowerCount:Int
let artistFollowerCount : Int
let label : String
}
この後、同じstruct
とdecodable
を拡張する同じenum
の拡張機能をCodingKey
で作成する必要があります。次に、この列挙型とそのキーとデータ型を使用してデコーダーを初期化する必要があります(キーは列挙型から取得され、データ型は来るか、構造自体から参照されると言う)
extension Track: Decodable {
enum TrackCodingKeys: String, CodingKey {
case id = "id"
case contributingArtistNames = "primaryArtistsNames"
case spotifyId = "spotifyId"
case name = "name"
case albumName = "albumName"
case albumImageUrl = "albumImageUrl"
case copyrightP = "copyrightP"
case copyrightC = "copyrightC"
case playlistCount = "playlistCount"
case trackPopularity = "trackPopularity"
case playlistFollowerCount = "playlistFollowerCount"
case artistFollowerCount = "artistFollowers"
case label = "label"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.id){
id = try trackContainer.decode(Int.self, forKey: .id)
}else{
id = 0
}
if trackContainer.contains(.contributingArtistNames){
contributingArtistNames = try trackContainer.decode(String.self, forKey: .contributingArtistNames)
}else{
contributingArtistNames = ""
}
if trackContainer.contains(.spotifyId){
spotifyId = try trackContainer.decode(String.self, forKey: .spotifyId)
}else{
spotifyId = ""
}
if trackContainer.contains(.name){
name = try trackContainer.decode(String.self, forKey: .name)
}else{
name = ""
}
if trackContainer.contains(.albumName){
albumName = try trackContainer.decode(String.self, forKey: .albumName)
}else{
albumName = ""
}
if trackContainer.contains(.albumImageUrl){
albumImageUrl = try trackContainer.decode(String.self, forKey: .albumImageUrl)
}else{
albumImageUrl = ""
}
if trackContainer.contains(.copyrightP){
copyrightP = try trackContainer.decode(String.self, forKey: .copyrightP)
}else{
copyrightP = ""
}
if trackContainer.contains(.copyrightC){
copyrightC = try trackContainer.decode(String.self, forKey: .copyrightC)
}else{
copyrightC = ""
}
if trackContainer.contains(.playlistCount){
playlistCount = try trackContainer.decode(Int.self, forKey: .playlistCount)
}else{
playlistCount = 0
}
if trackContainer.contains(.trackPopularity){
trackPopularity = try trackContainer.decode(Int.self, forKey: .trackPopularity)
}else{
trackPopularity = 0
}
if trackContainer.contains(.playlistFollowerCount){
playlistFollowerCount = try trackContainer.decode(Int.self, forKey: .playlistFollowerCount)
}else{
playlistFollowerCount = 0
}
if trackContainer.contains(.artistFollowerCount){
artistFollowerCount = try trackContainer.decode(Int.self, forKey: .artistFollowerCount)
}else{
artistFollowerCount = 0
}
if trackContainer.contains(.label){
label = try trackContainer.decode(String.self, forKey: .label)
}else{
label = ""
}
}
}
ここで、必要に応じてすべてのキーとデータ型を変更し、デコーダで使用する必要があります。
CodingKeyを使用すると、コード化可能またはデコード可能なプロトコルでカスタムキーを使用できます。
struct person: Codable {
var name: String
var age: Int
var street: String
var state: String
private enum CodingKeys: String, CodingKey {
case name
case age
case street = "Street_name"
case state
} }