Swfit

JSON 동일한 key에 다른 type의 value가 있는 경우 인코딩/디코딩 하기

GGShin 2023. 11. 12. 14:43

JSON parsing을 하다보면, key의 이름은 동일한데 서로 다른 타입의 값을 가진 데이터들을 파싱해야할 때가 있습니다.

 

예를 들어서, "value"라는 key인데 하나는 int값을 가지고 다른 하나는 string값을 가집니다.

 

// Int 타입인 경우
{
  "value": 1
}

// String 타입인 경우
{
  "value": "hat"
}

 

이러한 경우에는 Xcode에 에러가 보통 "typeMismatch"로 아래와 같이 나올 것입니다. 

typeMismatch(
Swift.String, Swift.DecodingError.Context(
codingPath: [
CodingKeys(stringValue: "ownedNfts", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "metadata", intValue: nil),
CodingKeys(stringValue: "attributes", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "value", intValue: nil)],
debugDescription: "Expected to decode String but found number instead.",
underlyingError: nil)
)

 

typeMismatch라고 알려주었기 때문에 타입에 문제가 있다는 것은 알 수 있지만, 

문제가 된 타입의 위계가 전부 나오다 보니 조금 보기가 불편합니다. 

그래도 잘 에러 문구를 따라가 보면, 마지막에 "value"라고 하는 부분에서 문제가 있었다는 것을 알려주고 있는 것입니다. 

 

"Expected to decode String but found number instead."

String을 decode하려고 했는데, 숫자가 발견되었다고 하네요. 

decode할 때 사용된 데이터 모델에 "value"는 String type으로 정의되어 있는데 받아온 JSON내 "value"는 숫자 형태였다는 것을 알 수 있습니다. 

 

이런 경우 "value" 의 값 타입을 잘못 정의해주어서 타입 미스매치 에러가 난 경우도 있지만,

앞서 말한 것 처럼 두개 이상의 타입을 갖는 경우도 있습니다.

 

전자의 경우는 타입을 고쳐주기만 하면 되고,

후자의 경우는 enum으로 분기 처리해서 해결하면 됩니다. 

 

 Solution 

struct NFTAttribute: Codable {
    let value: Value // 다수의 타입(String, Int)을 갖는 key
    let traitType: String
}

enum Value: Codable {
    case string(String) // String 타입의 값이 발견된 경우
    case number(Int) // Int 타입의 값이 발견된 경우

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        if let x = try? container.decode(Int.self) {
            self = .number(x)
            return
        }
        throw DecodingError.typeMismatch(Value.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Value"))
    }

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

 

1. enum의 case에 발견될 수 있는 타입을 나열해 줍니다.

2. init 에서 분기 처리를 해줍니다.

3. encode도 마찬가지로 분기처리를 해줍니다. 

 

제 경우는 "value"가 String이나 Int만 갖기 때문에 두 가지만으로 분기 처리를 해주었지만,

다른 타입의 경우는 해당되는 타입들로 분기처리를 해주면 됩니다.

반응형