본문 바로가기

iOS/DataParsing

[iOS] Codable 개념 뿌수기!! #3

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 타입의 인스턴스가 반환됩니다.

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가 존재하지 않는다는 에러를 방지할 수도 있다는 사실!!

 

이거때매 이틀을 꼬박 고생했네요..