[Swift] JSON Codable: Any Type Custom

Posted by Sung Kyungmo on August 10, 2020

Any type in Codable

API통신을 하다보면 같은 Model로 Response가 오지만 특정 값의 타입이 일정하지 않은 경우가 있습니다.

Case 1

1
2
3
4
{
  "id": 1234,
  "name": "Sung"
}

Case 2

1
2
3
4
5

{
  "id": "abcd",
  "name": "Sung"
}

Model

1
2
3
4
class Genre: Codable {
    let id: Int!
    let name: String!
}

Case1에서는 문제가 없지만 Case2에서는 id값이 String이기 때문에 typeMismatch Error가 발생합니다.

Codable에서는 Any 타입을 사용하기 어렵기 때문에 ObjC에 있는 NSNumber같이 특정타입으로 값을 꺼내쓸 수 있는 커스텀 타입을 만들어 활용할 수 있습니다.

AnyValue

  • 여기서는 Int, String이 들어갈 수 있는 AnyValue타입을 만들어보겠습니다.
  • 디코딩 가능한 타입이 없는경우에는 실패 에러를 발생시킵니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
enum AnyValue: Codable {
    case int(Int)
    case string(String)

    init(from decoder: Decoder) throws {
        if let int = try? decoder.singleValueContainer().decode(Int.self) {
            self = .int(int)
            return
        }

        if let string = try? decoder.singleValueContainer().decode(String.self) {
            self = .string(string)
            return
        }

        // 디코딩 가능한 타입이 없는경우 typeMismatch 에러 발생
        throw AnyValueError.typeMismatch
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        
        switch self {
        case .int(let value):
            try container.encode(value)
        case .string(let value):
            try container.encode(value)
        }
    }

    enum AnyValueError:Error {
        case typeMismatch
    }
}

AnyValue Extension

  • 이제 AnyValue타입의 값에 IntString값이 들어갈 수 있습니다.
  • 필요한 값을 편하게 가져다 쓰기 위해서 Extension으로 구현해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension AnyValue {
    var intValue: Int? {
        switch self {
        case .int(let value):
            return value
        case .string(let value):
            return Int(value)
        }
    }
    
    var stringValue: String? {
        switch self {
        case .int(let value):
            return String(value)
        case .string(let value):
            return value
        }
    }
}

AnyValue 사용

1
2
3
4
class Genre: Codable {
    let id: AnyValue!
    let name: String!
}
1
2
let intID = genre.id.intValue
let stringID = genre.id.stringValue