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. 이 적합성은 단순히 속성 이름에 해당하는 키 유형을 자동으로 생성합니다.이 키 유형은 단일 키 컨테이너에서 인코딩 / 디코딩하는 데 사용됩니다.
그러나이 자동 생성 적합성의 정말 멋진 기능 중 하나 는 프로토콜 을 준수 하는 enum” CodingKeys“(또는 typealias이 이름과 함께를 사용) 라는 유형에 중첩을 정의하면 CodingKeySwift 가이 를 자동으로 키 유형 으로 사용한다는 것 입니다. 따라서 속성이 인코딩 / 디코딩되는 키를 쉽게 사용자 지정할 수 있습니다.
이것이 의미하는 바는 다음과 같이 말할 수 있다는 것입니다.
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)에 떨어지는 유형 – 수동으로 제공하고, A 형
CodingKeyenum(이름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
당신이 당신의 이름을 바꾸면 스위프트 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이 배열의 마지막 키를 대체 할 a 를 반환합니다 .
예를 들어 속성 이름에 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")
답변
Swift 4.2에서는 필요에 따라 다음 3 가지 전략 중 하나를 사용하여 모델 객체 사용자 지정 속성 이름이 JSON 키와 일치하도록 할 수 있습니다.
#1. 사용자 지정 코딩 키 사용
다음 구현 으로 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")
*/
# 2. 뱀 케이스를 낙타 케이스로 사용하는 주요 코딩 전략
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을 당신은 codable 또는 복호 프로토콜에서 사용자 지정 키를 사용할 수 있습니다.
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
} }
답변
