Codableで複数の型を取りうる配列を持つJSONに対応する

とあるAPIのレスポンスをデコードする処理を書いていて、以下のように配列の中の型が複数あるケースに対応する必要がありました。

```json
{
    "products": [
        {
            "id": 1,
            "type": "FOO",
            "foo": "foofoo",
            "bar": "barbar"
        },
        {
           "id": 2,
           "type": "BAZ",
           "baz": "bazbaz",
           "qux": "quxqux"
        }
    ],
    "next": "xxxxxxx"
}
```

<br>

`id`と`type`のように、一部共通のプロパティも持っていて、これらを抽象的な型でまとめて表現したいというモチベーションもありました。

参考になる（というかほぼそのまま使える）コードをStackOverflowで見つけてなんとか実現できたので、その方法をご紹介したいと思います。

<br>

# やりたいこと
先ほどのJSONを以下のような型にデコードしたいです。

```swift
struct Response: Decodable {
    let products: [Product]
    let next: String?
}

protocol Product: Decodable {
    var id: Int { get }
    var type: ProductType { get }
}

enum ProductType: String, Decodable {
    case foo = "FOO"
    case baz = "BAZ"
}

struct ProductFoo: Product {
    let id: Int
    let type: ProductType
    let foo: String
    let bar: String
}

struct ProductBaz: Product {
    let id: Int
    let type: ProductType
    let baz: String
    let qux: String
}
```

<br>

# 標準のDecodableだけだとコンパイルエラーになる
上記の宣言をしても、このままだと以下のエラーが出てコンパイルできません。ProductがProtocolとして宣言されていて、具体的な型が確定できないからですね。

> Type 'Response' does not conform to protocol 'Decodable'

<br>

自動でDecodableに準拠できないのであれば、自前で`init(from:)`を実装して準拠させてみましょう。配列の中が任意のJSONになるのであれば、一旦`[Any]`としてデコードして、あとでProductFooかProductBazに変換してあげれば良いと考えました。

```swift
struct Response: Decodable {
    let products: [Product]
    let next: String?

    enum CodingKeys: String, CodingKey {
        case products
        case next
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.next = try container.decode(String?.self, forKey: .next)

        let anyProducts = try container.decode([Any].self, forKey: .products)  // コンパイルエラー
        ...
    }
}
```

しかし、これでもコンパイルエラーになります。

> No exact matches in call to instance method 'decode'

<br>

`container`はKeyedDecodingContainerという標準ライブラリが提供する構造体なのですが、こいつに`[Any]`にデコードするメソッドが定義されていないため、コンパイルエラーになってしまいます。

<br>

# [Any]でデコードできるようにする
どうしたものか困っていたところ、ドンピシャなStackOverflowの回答を見つけました。

https://stackoverflow.com/a/46049763

<br>

この回答にリンクされているGistのコードを導入すると、`[Any]`もしくは`[String: Any]`でデコードできるようになります。

https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24

<br>

若干タイポがあったりするので、ちょっと手直ししたコードがこちらです。

```swift
struct JSONCodingKeys: CodingKey {
    var stringValue: String

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
        self.init(stringValue: "\(intValue)")
        self.intValue = intValue
    }
}

extension KeyedDecodingContainer {
    func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
        var container = try nestedUnkeyedContainer(forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
        guard contains(key) else {
            return nil
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
        let container = try nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
        guard contains(key) else {
            return nil
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
        var dictionary = Dictionary<String, Any>()

        for key in allKeys {
            if let boolValue = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = boolValue
            } else if let stringValue = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = stringValue
            } else if let intValue = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = intValue
            } else if let doubleValue = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = doubleValue
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedDictionary
            } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedArray
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {
    mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
        var array: [Any] = []
        while isAtEnd == false {
            if let value = try? decode(Bool.self) {
                array.append(value)
            } else if let value = try? decode(Double.self) {
                array.append(value)
            } else if let value = try? decode(String.self) {
                array.append(value)
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
                array.append(nestedDictionary)
            } else if let nestedArray = try? decode(Array<Any>.self) {
                array.append(nestedArray)
            }
        }
        return array
    }

    mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
        let nestedContainer = try nestedContainer(keyedBy: JSONCodingKeys.self)
        return try nestedContainer.decode(type)
    }
}
```

<br>

これで`[String: Any]`もしくは`[Any]`でデコードできるようになります。

<br>

# [Any]でデコードしたあと任意の型に変換する
`init(from:)`の実装の完全版はこちらです。

```swift
struct Response: Decodable {
    let products: [Product]
    let next: String?

    enum CodingKeys: String, CodingKey {
        case products
        case next
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.next = try container.decode(String?.self, forKey: .next)

        // [1]
        let anyProducts = try container.decode([Any].self, forKey: .products)

        self.products = anyProducts.compactMap {
            // [2]
            guard let data = try? JSONSerialization.data(withJSONObject: $0, options: []) else { return nil }

            // [3]
            if let product = try? JSONDecoder().decode(ProductFoo.self, from: data) {
                return product
            } else if let product = try? JSONDecoder().decode(ProductBaz.self, from: data) {
                return product
            } else {
                return nil
            }
        }
    }
}
```

<br>

**[1]**  
`products`の配列は一旦`[Any]`としてデコードします。

**[2]**  
JSONSerializationでData型に変換します。

**[3]**  
JSONDecoderで具体的な型に変換します。try?で変換処理を実行し、型が合わなければelse ifでつなげて別の型で変換を実行します。

<br>

# 任意の型にデコードできるようになった
最初のJSONはこんな感じで変換できるようになりました。`products`内はProductFooもしくはProductBazという型に変換され、またProduct型として共通の振る舞いを持つこともできています。

```swift
let json = """
{
    "products": [
        {
            "id": 1,
            "type": "FOO",
            "foo": "foofoo",
            "bar": "barbar"
        },
        {
            "id": 2,
            "type": "BAZ",
            "baz": "bazbaz",
            "qux": "quxqux"
        }
    ],
    "next": "xxxxxxx"
}
"""

let decoder = JSONDecoder()
let response = try! decoder.decode(Response.self, from: json.data(using: .utf8)!)
let foo = response.products[0] as! ProductFoo
let baz = response.products[1] as! ProductBaz

print(foo.id)  // 1
print(foo.type)  // foo
print(foo.foo)  // foofoo
print(foo.bar)  // barbar
print(baz.id)  // 2
print(baz.type)  // baz
print(baz.baz)  // bazbaz
print(baz.qux)  // quxqux

response.products.forEach { print("id: \($0.id), type: \($0.type)") }
// id: 1, type: foo
// id: 2, type: baz
```