본문 바로가기
Swfit

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

by GGShin 2023. 11. 12.

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만 갖기 때문에 두 가지만으로 분기 처리를 해주었지만,

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

반응형