일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- PageViewController
- swift
- pbxfilesystemsynchronizedrootgroup
- SwiftGen
- storybaord
- UITableView
- CustomCode
- pbxgroup
- 2018 KAKAO BLIND RECRUITMENT
- 정보처리기사 실기
- 위클리챌린지
- Pod
- 정보처리기사 실기 요약본
- cocoapods
- IOS
- JSON
- Codable
- parse
- issecuretextentry
- Xcode
- dynamic height
- JSONSerialization
- Custom PageViewController
- 프로그래머스
- 정보처리기사
- programmers
- JSONParser
- Decodable
- 티스토리챌린지
- RealmSwift
- Today
- Total
iOS 개발일기
[Swift] JSONParser - Decodable(2) 본문
전편
이번 편에서는 Decodable에 대한 이해도를 조금 더 높힐 수 있는 시간을 가져보겠습니다.
Decodable은 사용하는 방법이 편하지만 생각보다 제약이 많기 때문에 정확하게 이해하고 사용해야합니다.
간단한 형식이라면 문제없이 사용할 수 있겠지만
언제나 간단한 파싱만 하지않는 법이죠...허허
어떠한 상황에서 에러가 나는지 어떻게 대처해야되는지 몇 가지 알아보도록 하겠습니다.
먼저, 기본 모델을 하나 만들어주겠습니다.
Struct User Model
struct User {
private(set) var uid: Int
private(set) var name: String
private(set) var age: Int
}
extension User: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uid = try container.decode(Int.self, forKey: .uid)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
}
private enum CodingKeys: String, CodingKey, CaseIterable {
case uid = "user_id",
name = "user_name",
age = "user_age"
}
}
이 모델을 이용하여 Decode를 진행해볼텐데요.
Decode를 진행하면서 받아오는 JSON Data의 형식이 Array인지 Dictionary인지를 먼저 파악해주어야 합니다.
JSON Data의 자료형에 따라서도 decode의 방법이 달라지게 되거든요.
JSON Data의 자료형이 Array일 경우에는 decode를 Dictionary로 할 경우에는 에러가 발생하게 됩니다.
반대로 Dictionary일 경우에 decode를 Array로 할 경우에도 당연히 에러가 발생하겠죠?
(당연한 이야기이겠지만 data에 자료가 하나만 들어있냐 여러 개가 들어있냐 차이에 따라 형태를 잘 지정해주셔야합나다.)
예를 들어보겠습니다.
JSON Data가 한 가지만 존재할 경우
let jsonString = """
{
"user_id" : 0,
"user_name" : "hoon",
"user_age" : 30
}
"""
func decode() {
//success, data에 하나의 자료만 들어있기 때문에 Dictionary로 decode를 진행합니다.
if let data = jsonString.data(using: .utf8) {
do {
if let user = try JSONDecodable().decode(User.self, from: data) {
print(user.id) //0
print(user.name) //"hoon"
print(user.age) //30
}
} catch {
print("parse error:", error)
}
}
//fail, data는 하나인데 Array로 decode를 할 경우 에러가 발생합니다.
if let data = jsonString.data(using: .utf8) {
do {
if let user = try JSONDecoder().decode([User].self, from: data) {
print(user.id)
print(user.name)
print(user.age)
}
} catch {
print("parse error:", error)
}
}
}
jsonString error: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
이렇게 data는 하나인데 Array로 decode를 하려고하면 에러가 발생하게 됩니다.
이 밑은 반대의 경우인데 접어놓겠습니다.
JSON Data가 여러 가지가 존재할 경우
let jsonString = """
[
{
"user_id" : 1,
"user_name" : "hoon",
"user_age" : 28
},
{
"user_id" : 2,
"user_name" : "joon",
"user_age" : 30
},
{
"user_id" : 3,
"user_name" : "min",
"user_age" : 32
}
]
"""
func decode() {
//fail, 여러 개의 data가 존재하지만 하나로 decode하려는 경우 에러가 발생합니다.
if let data = jsonString.data(using: .utf8) {
do {
if let users = try JSONDecoder().decode(User.self, from: data) {
users.forEach { user in
print(user.name)
}
}
} catch {
print("parse error:", error)
}
}
//success
if let data = jsonString.data(using: .utf8) {
do {
if let users = try JSONDecoder().decode([User].self, from: data) {
users.forEach { user in
print(user.name) //"hoon", "joon", "min"
}
}
} catch {
print("parse error:", error)
}
}
}
jsonString error: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
Decodable Model을 만들기 전에 먼저 parsing의 대상이 되는 data에 대한 형태를 먼저 파악하시면 도움이 되실 것 같네요.
다음으로 넘어가겠습니다.
드물게 일어나는 일이긴 합니다만 data의 형태가 변하는 것인데요.
예를 들어,
- 'user_id'가 null을 허용하거나
- 'user_id'가 삭제되었다면
기존에 User Model을 이용해서 decode하게 된다면 어떻게 될까요?
{
"user_id" : 0,
"user_name" : "hoon",
"user_age" : 30
}
↓
//"user_id"에 null 허용
{
"user_id" : null,
"user_name" : "hoon",
"user_age" : 30
}
또는
//"user_id" 삭제
{
"user_name" : "hoon",
"user_age" : 30
}
null 혹은 프로퍼티가 없을 경우에는 에러가 발생하게 됩니다.
alueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "user_id", intValue: nil)], debugDescription: "Expected Int value but found null instead.", underlyingError: nil))
위와 같이 값을 찾을 수 없다고 나오게되죠.
왜 이럴까요?
self.uid = try container.decode(Int.self, forKey: .uid)
여기에 decode 함수가 원인인데요.
보시면 반환형태가 Optional이 아니기 때문에 에러가 발생하게 되죠.
그러면 어떻게 해결할 수 있을까요?
기본값을 설정하거나 Optional을 허용할 수 있도록 해주는 방법 정도가 있는 것 같아요.
방법 1. 기본 값 설정
struct User {
private(set) var uid: Int
...
}
extension User {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
//set default value
self.uid = (try? container.decode(Int.self, forKey: .uid)) ?? 999
...
}
private enum CodingKeys: String, CodingKey, CaseIterable { ... }
}
try를 Optional처리를 해주어서 기본 값을 설정 해주는 방법이 있습니다.
기본 값이 꼭 필요한 변수에 적용하기에는 좋은 방법인 것 같습니다.
방법 2. Optional 허용
저는 개인적으로 이 방법을 자주 쓰는 것 같네요.
Optional을 허용하게되면 Model을 사용할 때 예외처리를 하기에 용이하기 때문에 자주쓰는 것 같아요.
(너무 많은 Optional을 사용하게되면 바인딩에 번거로움이 있긴하지만요...ㅎㅎ;)
decode() 함수는 해당 프로퍼티에 값이 null 또는 존재하지 않는다면 에러가 발생하지만
decodeIfPresent() 함수는 반환형태가 Optional이므로 null이나 프로퍼티가 존재하지 않더라도 nil처리를 할 수 있죠.
변수를 Optional로 변경하여 nil을 가지는 것과
struct User {
private(set) var uid: Int?
...
}
extension User: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyed: CodingKeys.self)
self.uid = try container.decodeIfPresent(Int.self, forKey: .uid)
...
}
private enum CodingKeys: String, CodingKey, CaseIterable { ... }
}
decodeIfPresent()를 이용하여 방법 1. 처럼 기본 값을 설정해줄 수도 있어요.
self.uid = (try container.decodeIfPresent(Int.self, forKey: .uid)) ?? 999
오늘은 에러가 발생하는 상황들과 대처방법을 알아보았습니다.
저도 공부를 하면서 쓰는거라 부족한 점이 많은지라 지적은 언제나 감사하게 받겠습니다.
'iOS > Swift' 카테고리의 다른 글
[Swift] Custom PageViewController (0) | 2022.03.22 |
---|---|
[Swift] JSONParser - JSONSerialization (0) | 2022.03.16 |
[Swift] JSONParser - Decodable(1) (0) | 2022.03.14 |
[Swift] iOS 15, UINavigationBar barTintColor 적용 방법 (0) | 2021.09.28 |
[Swift] String.addingPercentEncoding() 특수문자 인코딩하기 (0) | 2021.03.22 |