[Swift]Property Wrappersを使ってJSONの型が不確定な値をString型でデコードする方法

JSON形式のデータを取得しCodableに適合したstruct型に変換するといったケースにおいて、Stringとして扱いたいが場合によってInt型やDouble型になるかもしれない値が含まれる場合に有効な方法を紹介します。

# 動作環境
- macOS Big Sur 11.3.1
- XCode 12.5.1 
- Swift 5.4.2

# 通常のString型の値をデコードする場合

まず`identifier`と`name`という値を持つ`Item`という型を定義し、JSONからデコードする例を紹介します。

```swift
struct Item: Decodable {
    let identifier: String
    let name: String
}

let jsonString: String = """
    {
        "identifier": "123",
        "name": "Hello World"
    }
    """

let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let item = try! decoder.decode(Item.self, from: jsonData)
print("identifier:",item.identifier)
print("name:",item.name)
```

```:実行結果
identifier: 123
name: Hello World
```

`identifier`の値は`123`とあるものの、ダブルクォーテーションで囲われているため問題なくデコードできます。

# 値が数値の場合

`identifier`の値がダブルクォーテーションで囲われておらず、数値として扱われる場合はどうでしょうか？

```swift
let jsonString: String = """
    {
        "identifier": 123,
        "name": "Hello World"
    }
    """
```

```:実行結果
fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [Codingidentifiers(stringValue: "identifier", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
```

数値である`identifier`を`String`としてデコードしようとしたため、エラーが発生してしまいました。

# 普通に`init(from decoder: Decoder)`を実装する
`Item`のデコード処理を以下のように書き換えます。

```swift
struct Item: Decodable {
    let identifier: String
    let name: String

    enum CodingKeys: String, CodingKey {
        case identifier
        case name
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        identifier = try (try? container.decode(String.self, forKey: .identifier))
            ?? (try "\(container.decode(Int.self, forKey: .identifier))")
        name = try container.decode(String.self, forKey: .name)
    }
}
```

```:実行結果
identifier: 123
name: Hello World
```

エラーが起きることなくデコードに成功しました。しかし、この方法では`Item`にプロパティが増えた場合に特別な処理を必要としない場合でも逐一デコード処理を追加する必要があり、機能拡張が煩雑になります。

# Property Wrapperを使う方法

同じデコード処理を記述するのに今度はSwift 5.1 から利用可能な `Property Wrappers`という機能を使って実装してみましょう。
まず以下のような`StringValue`という`PropertyWrapper`を定義します。ついでに`Int`型以外にも`Double`型や`Bool`型にも対応しています。

```swift
@propertyWrapper
struct StringValue: Decodable {
    var wrappedValue: String

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        try wrappedValue = {
            return try (try? container.decode(String.self))
                ?? (try? "\(container.decode(Int.self))")
                ?? (try? "\(container.decode(Double.self))")
                ?? (try "\(container.decode(Bool.self))")
        }()
    }
}
```
そして、`StringValue`を`Item`の`identifier`に適用します。

```swift
struct Item: Decodable {
    @StringValue
    private(set) var identifier: String
    let name: String
}
```

```:実行結果
identifier: 123
name: Hello World
```

この方法でもデコードに成功しました。こちらは最初に`PropertyWrapper`を用意する必要があるものの、一度定義してしまえば逐一デコード処理を書き直さずとも良くなります。

# サンプル
最後に`Property Wrappers`を使う場合のソースコード全文を掲載します。

```swift
import Foundation

@propertyWrapper
struct StringValue: Decodable {
    var wrappedValue: String

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        try wrappedValue = {
            return try (try? container.decode(String.self))
                ?? (try? "\(container.decode(Int.self))")
                ?? (try? "\(container.decode(Double.self))")
                ?? (try "\(container.decode(Bool.self))")
        }()
    }
}

struct Item: Decodable {
    @StringValue
    private(set) var identifier: String
    let name: String
}

let jsonString: String = """
    {
        "identifier": 123,
        "name": "Hello World",
    }
    """

let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let item = try! decoder.decode(Item.self, from: jsonData)
print("identifier:",item.identifier)
print("name:",item.name)
```