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
이 이름과 함께를 사용) 라는 유형에 중첩을 정의하면 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)에 떨어지는 유형 – 수동으로 제공하고, A 형
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
당신이 당신의 이름을 바꾸면 스위프트 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
} }