[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からデコードする例を紹介します。
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
の値がダブルクォーテーションで囲われておらず、数値として扱われる場合はどうでしょうか?
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
のデコード処理を以下のように書き換えます。
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
型にも対応しています。
@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
に適用します。
struct Item: Decodable {
@StringValue
private(set) var identifier: String
let name: String
}
identifier: 123
name: Hello World
この方法でもデコードに成功しました。こちらは最初にPropertyWrapper
を用意する必要があるものの、一度定義してしまえば逐一デコード処理を書き直さずとも良くなります。
サンプル
最後にProperty Wrappers
を使う場合のソースコード全文を掲載します。
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)
Author And Source
この問題について([Swift]Property Wrappersを使ってJSONの型が不確定な値をString型でデコードする方法), 我々は、より多くの情報をここで見つけました https://qiita.com/idotatsuki/items/38f3979aaa2e82493dd3著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .