이전 포스트는 Codable의 Encodable에 대해 알아 보았습니다.
이번 포스트는 Codable의 Decodable에 대해 알아보도록 하겠습니다.
Decodable
JSON 데이터를 디코딩 하는 방법에 대해 알아보도록 하겠습니다.
Decodable을 이용하면 JSON을 쉽게 처리할 수 있습니다.
# 디코딩 방법
decode 메서드는 두개의 파라미터가 필요합니다.
첫번째 파라미터는 디코딩할 타입을 정의한다.
반드시 Decodable이나 Codable 프로토콜을 채택해야됩니다.
struct Person: Codable {
var name: String
var age: Int
var birthDate: String
var address: String?
}
두번째 파라미터에는 json이 저장되어있는 data를 전달합니다.
let jsonData =
"""
{
"name" : "Jinnify",
"age" : 29,
"birthDate" : "2019-04-29T01:30:00Z",
"address" : "동대문구 답십리"
}
""".data(using: .utf8)!
결과적으로 decode방법
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Person.self, from: jsonData)
print(result)
} catch {
print(error)
}
# JSON의 Key와 프로퍼티가 동일하지 않은 경우
위와 같이 JSON의 Key가 Codable을 따르는 프로퍼티와 이름이 동일할 경우는 간단하게 처리됩니다.
하지만 JSON의 Key와 타입의 이름을 다르게 하고자 한다면 CodingKeys
를 정의 해주어야 합니다.
서버에서 JSON이 아래와 같이 내려왔다고 해보죠
let jsonData =
"""
{
"name" : "Jinnify",
"age" : 29,
"birth_date" : "1991-04-29",
"address" : "동대문구 답십리"
}
""".data(using: .utf8)!
자세히 보면 birth_date 처럼 snake_case형태로 내려왔습니다.
사실 Swift는 snake_case보다 lowerCamelCase를 사용합니다.
lowerCamelCase로 변환하고 싶거나, 커스텀으로 key이름을 변환하고 싶을때
아래와 같이 CodingKeys
를 정의합니다.
- birth_date -> birthDate로 변경
- address를 juso로 변경
struct Person: Codable {
var name: String
var age: Int
var birthDate: String
var juso: String?
enum CodingKeys: String, CodingKey {
case name, age
case birthDate = "birth_date"
case juso = "address"
}
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Person.self, from: jsonData)
print(result)
} catch {
print(error)
}
/*
결과
Person(name: "Jinnify", age: 29, birthDate: "1991-04-29", juso: Optional("동대문구 답십리"))
*/
아래와 같이 접근 가능!
let result = try decoder.decode(Person.self, from: jsonData)
result.juso
result.birthDate
result.age
result.name
💡간단하게 snake_case로 jsonData가 내려올경우 lowerCamelCase만 변환하는 방법
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
# Date Decoding 방법
구조체의 birthDate를 String에서 Date타입으로 변경하고, jsonData의 birthDate는 ISO8601형식을 변경하였다.
let jsonData =
"""
{
"name" : "Jinnify",
"age" : 29,
"birthDate" : "2019-04-29T01:30:00Z",
"address" : "동대문구 답십리"
}
""".data(using: .utf8)!
struct Person: Codable {
var name: String
var age: Int
var birthDate: Date
var address: String?
}
위의 Date타입으로 지정해주었지만 에러가 발생합니다. - typeMismatch
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "birthDate", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
별도의 Date의 디코딩을 해주지 않으면 Double값으로 디코딩을 시도합니다.
birthDate의 형식을 보면 ISO8601 표준을 사용하고 있습니다.
아래와 같이 dateDecodingStrategy 를 ISO8601로 변경해주면 문제는 해결됩니다.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let result = try decoder.decode(Person.self, from: jsonData)
print(result)
} catch {
print(error)
}
/*
결과
Person(name: "Jinnify", age: 29, birthDate: 2019-04-29 01:30:00 +0000, address: Optional("동대문구 답십리"))
*/
# JSON의 Key와 Value가 변경되거나 null인 경우
서버의 데이터를 믿지마라!
서버에서 내려주는 JSON데이터는 변경될 수 있습니다.
실무에선 어느 상황이든 대비를 해야되기 됩니다.
기존 데이터 :
{
"name" : "Jinnify",
"age" : 29
}
변경된 데이터 :
age가 더이상 필요없어져서 더 이상 서버에서 내려오지 않는 경우
{
"name" : "Jinnify",
}
age라는 데이터가 쓸모가 없어져 내려오던 데이터가 갑자기 내려오지 않는다면, keyNotFound
에러가 발생합니다.
let jsonStr = """
{
"name" : "Jinnify",
}
"""
struct Person: Codable {
var name: String
var age: Int
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Person.self, from: jsonData)
print(result)
} catch {
print(error)
}
/*
결과
keyNotFound(CodingKeys(stringValue: "age", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"age\", intValue: nil) (\"age\").", underlyingError: nil))
*/
드물긴 하지만 충분히 가능성 있는 에러이기 때문에 대비를 해야됩니다.
기존의 Codable 모델이 간단하게 사용되지만
struct Person: Codable {
var name: String
var age: Int
}
내부적으로는 자동으로 CodingKeys, init(from decoder: Decoder) 이 자동으로 생성되어 집니다.
struct Person: Codable {
var name: String
var age: Int
private enum CodingKeys: String, CodingKey {
case name, age
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
}
}
init(from decoder: Decoder)이 부분을 통해 직접 코드를 작성하여 기본값을 넣어주는 방식으로 에러를 해결 할 수 있습니다.
let jsonStr = """
{
"name" : "Jinnify",
}
"""
struct Person: Codable {
var name: String
var age: Int
private enum CodingKeys: String, CodingKey {
case name, age
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = (try? values.decode(String.self, forKey: .name)) ?? ""
age = (try? values.decode(Int.self, forKey: .age)) ?? 0
}
}
JSON의 특정 키가 없는 경우 빈 디폴트 값을 주어 해결을 했습니다.
또는 모든 타입에 옵셔널 타입 으로 처리하는 방법도 있습니다.
struct Person: Codable {
var name: String?
var age: Int?
}
하지만 모든 타입을 옵셔널로 처리할 경우 옵셔널 바인딩을 매번 해주어야 됨으로 번거로울 수 있습니다.
여기까지 JSONDecoder를 이용해서 Data타입을 디코딩 하는 방법과
CodingKeys를 이용해서 JSON의 키를 커스텀 방법,
dateDecodingStrategy를 통한 Date 디코딩 방법,
null일 경우 처리 default 처리 방법에 대해 알아보았습니다.
'Swift' 카테고리의 다른 글
[Swift] Codable - Encoding 방법 (0) | 2019.12.30 |
---|---|
[Swift] 2. 예제로 알아보는 함수의 합성(Composition) (0) | 2019.08.12 |
[Swift] Swift Style Guide 정리 (1) | 2019.07.29 |
[Swift] 1. 순수함수, Pure Function (Functional Programming in Swift) (0) | 2019.07.25 |
[Swift] 문자열 다루기 (0) | 2019.07.25 |