Codable protocol 란?
Swift 표준 라이브러리에서 데이터를 encoding 또는 decoding하기위한 접근을 표준화했습니다. 고 녀석이 바로 Codable인데요. Codable은 Encodable과 Decodable의 typealias입니다. 타입으로 사용하거나 제너릭 형태로 제약을 걸 경우에 어떤 타입이든 Encodable 또는 Decodable 두 프로토콜의 요구조건을 만족시키게 됩니다.
Encodable 또는 Decodable 프로토콜을 적용한 Codable타입의 데이터는 Encodable 또는 Decodable 프로토콜을 구현하므로 external representation(== JSON, 인스턴스 등)로 인코딩 또는 디코딩 할 수 있습니다. Encodable또는 Decodable을 포함한 Codable타입으로 쉽게 정의할 수 있습니다.(Codable 프로토콜은 자동으로 Encode, Decode 할수있음!!)
- Encodable = Codable타입의 자료 구조를 external representation(ex JSON) JSON으로 인코딩
- Decodable = Codable타입의 데이터를 내가 커스텀으로 구현한 Codable타입의 자료 구조로 디코딩(개꿀...)
어떻게 Codable 프로토콜을 사용할 수 있는가?
Codable타입으로 선언하면 자동으로 Encodable, Decodable을 요구조건을 만족시키므로 PropertyListEncoder,JSONEncoder, JSONDecoder 클래스를 통해 encode 또는 decode등을 사용할 수 있습니다.
이때 Codable을 쓰려면 모든 프로퍼티가 Codable타입 이어야 합니다.
struct Coord: Codable {
var x: Double
var y: Double
}
struct CurrentPoint : Codable{
var time: Int
var point: Coord
}
여기서 CurrentPoint의 point프로퍼티는 내가 만든 구조체이지만 Codable타입이기에 CurrentPoint 또한 Codable 타입이 일치 됩니다!! 구조체 뿐 아니라 Array, Dictrionary, Optional 타입 전부 Codable로 사용할 수 있습니다.
(Codable을 지원하지 않는 경우 그냥 Encodable 또는 Decodable을 사용해도 됩니다.)
데이터를 파싱할 때 URLRequest + URLSession의 dataTask(with:) 이후의 데이터는 JSONDecoder().decode(_:from:)을 통해 Codable타입의 인스턴스를 얻을 수 있습니다. Alamofire을 사용할때 responseData또한 마찬가지 입니다.
Alamofire의 responseDecodable(of:)는 위에서 구현한 JSONDecoder().decode(_:from:)을 수행한 Codable 타입의 인스턴스가 반환됩니다.
Coding Keys란?
지금부터 예시는 것은 Decodable 기준으로 서버에서 데이터를 파싱하는 관점으로 볼 것입니다.
이전 글에서 RESTful API를 받아오기 위해 구조체를 선언할 때 서버에서 정의된 JSON형식의 key가 구조체의 프로퍼티로 선언합니다. 여기서 서버에서 정의된 JSON의 키들 중에서 몇 몇 개만 받아오고 싶은 경우 해당 key를 프로퍼티로 하는 구조체를 선언 한 후에 JSONDecoder를 통해 받아올 수 있습니다. 하지만 서버에서 정의된 JSON의 키와 해당 키를 프로퍼티로하는 구조체의 프로퍼티명이 다를 경우에는 문제가 발생합니다!!! 사용하지 않을 키는 구조체의 프로퍼티로 선언하지 않거나!! 서버에서 정의된 JSON의 key 이름과 프로퍼티명이 같아야만 decode를 할 수 있습니다.
"프로퍼티 명을 다른걸로 하고 싶은데?"
이럴때 Coding Keys 프로토콜의 enum을 사용해서 서버JSON의 key이름이 아닌 내가 원하는 프로퍼티명으로 네이밍을 할 수 있습니다 +_+
ex) 서버 JSON의 키가 snake case 표현을 했다면?!
{
"time" : 3,
"point" : {
"x_prev_point" : 10.2223,
"y_pref_point" : 23.3222
}
}
위에서의 JSON 데이터를 Swift 객체 Codable로 파싱하기 위해 구조체를 선언할 때 위에서 정의한 Coord 타입의 구조체를 쓸 경우 JSON key와 일치하지 않아 에러가 발생해 JSONDecode.decode(_:with:)를 사용할 수 없습니다.
프로퍼티명을 x_prev_point말고 x로 사용하기 위해선 Coding Keys를 사용하면 됩니다. 이때 서버의 json key 이름이 괜찮은 것은 그냥 case 프로퍼티명으로 선언만 하면 됩니다.
struct Coord: Codable {
var x: Double
var y: Double
enum CodingKeys: String, CodingKey {
case x = "x_prev_point"
case y = "y_prev_point"
}
}
struct CurrentPoint : Codable{
var time: Int
var point: Coord
}
Coding Keys 특징
- CodingKeys 열거형을 정의하는 구조체의 프로퍼티들이 전부 case로 존재해야 합니다.
- 만약 extension으로 Decodable, Encodable에서 특정 프로퍼티를 빼고 싶어 CodingKeys case에서 제외한 경우 기본값을 넣어줘야 합니다. 그래야 Codable 요구 조건이 만족됩니다. (약간 좀 깐깐한데 사용하면 편한 스타일,,)
- 서버의 특정 key와 내가 선언한 프로퍼티명이 다를 경우 CodingKeys 타입의 raw-value로 String을 추가해야 합니다. (그래야 encoding, decoding시에 사용할 수 있습니다.)
뭐니뭐니해도 진짜 편합니다. JSON Object의 키에대한 값을 일일이 대입하지않고 Codable을 만족한다면 JSONDecode().decode 를 사용하기 만하면 바로 Codable타입의 자료형을 인스턴스로 사용할 수 있으니까요.
Q> 서버의 JSON key 값이 비어있거나 없을 경우에 어떻게 할까? 해당 프로퍼티에 "?" 옵셔널을 붙여주면 됩니다.
만약 서버에서 JSON 특정 key value 데이터가 들어오지 않는데 나는 디폴트값을 넣어주고 싶으면
ex) // name 이 들어오지 않을 경우 "Guest"를 사용하고 싶다면?
struct userInfo: Codable {
let email: String?
let name: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name) ?? "Guest"
email = try values.decode(String.self, forKey: .email)
}
}
함수들 간단 요약
- Container는 주어진 Key Type으로 지정된 decoder에 저장된 데이터를 데이터를 반환하고.
- values.decode는 컨테이너(decoder에 저장된 데이터)에서 forKey (CodingKey타입의) 특정 키에 맞는 데이터를 컨테이너에서 찾아서 String으로 반환한다.
- 근데 decodeIfPresent(_:forKey:)는 위에서 맞는 데이터가 없으면 nil반환
요기서 뽀인트는 decodeIfPresent(_:forKey:)는 옵셔널을 반환하는데 요때 옵셔널이라면 Guest로 초기화를 할 수 있다는 사실!! 만약 "??"를 사용 안하면 해당 프로퍼티는 옵셔널 타입이어야 합니다!!
decode는 non-optional을 반환하구요. +_+
그리고 서버의 JSON key가 존재하지 않을 때 디코더에 지정된 키 == Codable타입의 구조체,클래스에 특정 프로퍼티 는 null 인데 이때도 nil을 반환하는데 요때도 "??"를 써서 key가 존재하지 않는다는 에러를 방지할 수도 있다는 사실!!
이거때매 이틀을 꼬박 고생했네요..
'iOS > DataParsing' 카테고리의 다른 글
[iOS] Codable로 String타입의 JSON데이터-> Data타입으로 Decode. | String -> UIImage decode? #5 (2) | 2022.09.29 |
---|---|
[iOS] Codable 프로토콜 사용 유무 차이.. 완죤 대박 #4 (0) | 2022.09.07 |
[iOS] JSON형식의 데이터 파싱 뿌수기!! with Alamofire #2 (0) | 2022.09.06 |
[iOS] JSON형식의 데이터 파싱 뿌수기!! with URLRequest, URLSession #1 (0) | 2022.09.04 |