본문 바로가기
Xcode/Swift - PlayGround

PlayGround) Codable ANY 타입 처리 방법

by 후르륵짭짭 2020. 12. 19.
728x90
반응형

 

안녕하십니까 후르륵짭짭 입니다.

이번에는 Codable에서  ANY 타입을 파싱하는 방법에 대해 알아보려고 합니다.

이번에 과제를 하면서 Rest API로 내려오는 데이터가 "String"과 "INT"형으로 둘다 내려온다면

ANY 타입으로 받으려고 했는데,,,, 오류가 뜨는 겁니다...

그래서 해결 방법을 찾게 됐습니다..

 

** 해결 방법 **

이런 경우는 자주 없을 것이라 생각하지만, JSON으로 내려오는 데이터가 여러가지의 타입을 가지게 된다면 

여러가지 타입을 가질 수 있도록 만들어줘야합니다.

일단 어느 부분에서 오류가 생기는지 확인 할 수 있는 방법에 대해 설명하겠습니다.

 

 - 오류확인 위치 확인하는 방법 -

//Data Model

struct ItemModel : Codable {
        
    let name : String?
    let url : String?
    let shop : String?
    let start_datetime : String?
    let end_datetime : String?
    let price : Int?
    let image_list : [String]?

}

데이터 모델은 이렇게 위에 처럼 구성이 되어 있고

//Input Json Data

let jsonReq = """
{
                          "id": -1,
                          "type": "item",
                          "start_datetime": -1,
                          "name": "",
                          "shop": "cjmallplus",
                          "order": 12,
                          "url": "",
                          "image": "",
                          "price": 0,
                          "live": null
}
"""

입력으로 보내지는 JSON 데이터는 위와 같이 생겼다고 합시다.

그러면 위의 내용을 보면 Start_dateTime은 Int형으로 입력으로 주어지지만,

Model은 String 타입으로 되어 있습니다.

그런데 위와 같이 파싱을 하게 된다면 오류가 발생하게 되는데,

//Parsing Code


func main(){

let data = jsonReq.data(using: .utf8)

do {
    let decode = try JSONDecoder().decode(ItemModel.self, from: data!)
    print(decode)
    
}
catch DecodingError.keyNotFound(let key, let context){
    print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.valueNotFound(let key, let context){
    print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.typeMismatch(let type, let context) {
    print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
    print("data found to be corrupted in JSON: \(context.debugDescription)")
}
catch let jsonError{
    print(jsonError.localizedDescription)
}

}

위와 같이 코드를 적어준다면 어느 부분에서 오류가 발생하는지 쉽게 알 수 있습니다.

결과로는 "type mismatch for type String in JSON: Expected to decode String but found a number instead." 이렇게

결과가 나왔습니다.

타입이 잘 못 됐다고 알려주는군요,,,,,

 

** ANY 타입 문제 해결 **

타입이 여려가지가 될 수 있는 상활일 때는 두가지 방법이 있습니다.

하나는 Init(decoder:)로 해결 하는 방법과 enum으로 해결하는 방법이 있습니다.

 - Init(decoder:)로 해결하는 방법 

init(decoder:)의 내용을 본다면 주어진 디코더로 새로운 객체를 디코딩하여 생성하는 것이라고 되어 있고

논의하기에, 이 생정자는 만약에 decode하는 과정 중 문제가 생긴다면 에러를 반환한다고 되어 있습니다.

예 맞습니다.

이렇게 하나씩 decode를 해줘야 문제를 해결 할 수 있습니다.

struct ItemModel : Codable {

    let name : String?
    let url : String?
    let shop : String?
    let start_datetime : String?
    let end_datetime : String?
    let price : Int?
    let image_list : [String]?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        name = try container.decode(String?.self, forKey: .name)
        url = try container.decode(String?.self, forKey: .url)
        shop = try container.decode(String?.self, forKey: .shop)
        price = try container.decode(Int?.self, forKey: .price)
        
        if let endDateTIme = try? container.decode(String?.self, forKey: .end_datetime) {
            end_datetime = endDateTIme
        }
        else{
            end_datetime = nil
        }
        
        if let imagelist = try? container.decode([String]?.self, forKey: .image_list) {
            image_list = imagelist
        }
        else{
            image_list = nil
        }
        
        if let dateTime = try? container.decode(String.self, forKey: .start_datetime){
            start_datetime = dateTime
        }
        else{
            let intType = try container.decode(Int.self, forKey: .start_datetime)
            
            start_datetime = String(intType)
        }
        
    }
    
}

//결과
ItemModel(name: Optional(""), url: Optional(""), shop: Optional("cjmallplus"), start_datetime: Optional("-1"), end_datetime: nil, price: Optional(0), image_list: nil)

위와 같이 코드를 작성하는 방법이 있습니다.

하나씩 확인을 해보면 

let container = try decoder.container(keyedBy: CodingKeys.self)

decoder.container는 현재 Codable 구조체에 해당하는 모든 Key 값들을 가져오게 됩니다. 

그래서 Returns the data stored in this decoder 라고 적혀 있습니다.

그리고 우리는 각각의 Key 값들 모두!!!! 다!!!! 아래 처럼 적어줘야합니다.

name = try container.decode(String?.self, forKey: .name)

이렇게 하면 현재 name Key를 String 옵셔널 타입으로 디코딩 하겠다는 것을 의미합니다.

하지만 만약 JSON에 Key가 존재 하지 않을 수도 있고 타입도 다를 수가 있을 겁니다.

만약 JSON에 Key가 존재 하지 않는다면

if let endDateTIme = try? container.decode(String?.self, forKey: .end_datetime) {
            end_datetime = endDateTIme
        }
        else{
            end_datetime = nil
        }

위와 같이 옵셔널 바인딩으로 해결을 해주시면 됩니다.

타입이 다른 경우에서도 위와 동일한 방식으로 아래 처럼 해주면 됩니다.

if let dateTime = try? container.decode(String.self, forKey: .start_datetime){
            start_datetime = dateTime
        }
        else{
            let intType = try container.decode(Int.self, forKey: .start_datetime)
            
            start_datetime = String(intType)
        }

또 다른 방식으로는 DO - Catch 문을 이용해서 해결하면 됩니다.

do{
            start_datetime = try container.decode(String.self, forKey: .start_datetime)
        }
        catch{
            let intType = try container.decode(Int.self, forKey: .start_datetime)

            start_datetime = String(intType)
        }

 

 - Enum 방식으로 해결하는 방법 

enum DynamicJsonProperty : Codable{
    
    case string(String)
    case int(Int)
    
    init(from decoder : Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        
        //Decode the property
        do {
            let stringValue = try container.decode(String.self)
            self = .string(stringValue)
        }
        catch DecodingError.typeMismatch{
            let intValue = try container.decode(Int.self)
            self = .int(intValue)
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let value):
            try container.encode(value)
        case .int(let value):
            try container.encode(value)
        }
    }
    
}

위의 방식과 다른 것은 별로 없습니다.

하지만 가장 큰 차이는 CodingKey를 사용하냐? 아니면 singleValueContainer를 사용하냐 차이라고 생각합니다.

위에서 decode.container 방식은 모든 Key Type을 제공하는 방면에 enum은 하나의 Key에 적용하기 때문에 

오직 Single Value를 제공하게 됩니다.

그리고 이 Single Value를 아래 코드로 분류 해줍니다.

//Decode the property
        do {
            let stringValue = try container.decode(String.self)
            self = .string(stringValue)
        }
        catch DecodingError.typeMismatch{
            let intValue = try container.decode(Int.self)
            self = .int(intValue)
        }

만약 우리가 원하는 String 값으로 해독이 된다면 String Case로 값을 넣어주고 그게 아니라

DecodingError에 TypeMismatch가 발생한다면 Int 값으로 넣어주도록 합니다.

이렇게 하면 우리가 원하는 방식대로 해결하게 됩니다.

하지만 Enum으로 할 경우에는 Decode 뿐만 아니라 Encode 까지 해줘야합니다.

func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let value):
            try container.encode(value)
        case .int(let value):
            try container.encode(value)
        }
    }

이것은 container에 값을 인코딩 해서 넣어주게 되는 방식 입니다.

 

그리고 마지막에 데이터 파싱을 할때 

func main(){

let data = jsonReq.data(using: .utf8)

do {
    let decode = try JSONDecoder().decode(ItemModel.self, from: data!)
    print(decode)
    
    switch decode.start_datetime {

    case .int(let value):
        print("Int Type : \(value)")
    case .string(let value):
        print("String Type : \(value)")

    default :
        break
    }
}
catch DecodingError.keyNotFound(let key, let context){
    print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.valueNotFound(let key, let context){
    print("could not find key \(key) in JSON: \(context.debugDescription)")
}
catch DecodingError.typeMismatch(let type, let context) {
    print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
} catch DecodingError.dataCorrupted(let context) {
    print("data found to be corrupted in JSON: \(context.debugDescription)")
}
catch let jsonError{
    print(jsonError.localizedDescription)
}

}

이렇게 switch 문으로 Case 별로 나눠서 값을 가지게 할 수 있습니다.

지금까지 ANY 타입 형태로 값을 Codable로 해독하는 방법에 대해 공부했습니다.

내용이 길어서 쫌 힘들었네요

모두 즐코하세요

 

소스코드 :

github.com/HururuekChapChap/Swift_Study/blob/master/PlayGround/Any_Type_Codable_Tutorial.playground/Contents.swift

 

참고 사이트 : 

codable에서 생기는 오류 찾는 방법 :

www.hackingwithswift.com/forums/swiftui/decoding-json-data/3024

 

SOLVED: Decoding JSON data - Error response – SwiftUI – Hacking with Swift forums

Link copied to your pasteboard.

www.hackingwithswift.com

 

ANY 타입으로 된 프로퍼티를 Codable로 파싱하는 방법 : 

stackoverflow.com/questions/48297263/how-to-use-any-in-codable-type

 

How to use Any in Codable Type

I'm currently working with Codable types in my project and facing an issue. struct Person: Codable { var id: Any } id in the above code could be either a String or an Int. This is the reason ...

stackoverflow.com

 

Any 타입으로 된 프로퍼티를 Enum으로 해결하는 방법 : 

stackoverflow.com/a/46761102/13065642

 

Swift structures: handling multiple types for a single property

I am using Swift 4 and trying to parse some JSON data which apparently in some cases can have different type values for the same key, e.g.: { "type": 0.0 } and { "type": "12.44591406" }...

stackoverflow.com

 

Swift Codable의 Decode 방법 : 

stackoverflow.com/a/48069236/13065642

 

Swift 4 - Decoding --> [Orders.CodingKeys]' does not conform to expected type 'CodingKey'

I've been trying to decode a json with custom decoding but i am running into a problem that i haven't been able to solve and I am running out of idea. I commented out the additionalInfo in the Order

stackoverflow.com

 

String을 Data 타입으로 변경하는 방법 : 

www.objc.io/blog/2018/02/13/string-to-data-and-back/

 

Swift Tip: String to Data and Back

When to force-unwrap, when to check for nil

www.objc.io

 

Enum 값을 Switch 문으로 처리하능 방법 :

soooprmx.com/archives/7163

 

enum 타입 사용법 정리 - Swift · Wireframe

Emumerations “열거”타입은 임의의 관계를 맺는 값들을 하나의 타입으로 묶어서 타입-안전한 방식으로 다룰 수 있게 해준다. C에서도 enum 키워드를 이용해서 열거체를 선언할 수 있었는데, C의 열

soooprmx.com

 

728x90
반응형

댓글