「派生型プロパティを Decodable で扱う」 https://hitorigoto.zumuya.com/200716_decodableProtocolProperty にインスパイヤされたコード。いじってたらPropertyWrapperがなくなってしまった…
// 汎用コード | |
struct CustomCodingKey: CodingKey, ExpressibleByStringLiteral { | |
let stringValue: String | |
let intValue: Int? = nil | |
init?(stringValue: String) { self.stringValue = stringValue } | |
init?(intValue: Int) { return nil } | |
init(stringLiteral value: String) { stringValue = value } | |
init(_ value: String) { stringValue = value } | |
} | |
protocol PolymorphicDecodable: Decodable { | |
static var hintKeyName: String { get } | |
init?(hint: String, decoder: Decoder, container: KeyedDecodingContainer<CustomCodingKey>) throws | |
} | |
extension PolymorphicDecodable { | |
static var hintKeyName: String { "type" } | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CustomCodingKey.self) | |
let hintKey = CustomCodingKey(Self.hintKeyName) | |
let hint = try container.decode(String.self, forKey: hintKey) | |
guard let v = try Self(hint: hint, decoder: decoder, container: container) else { | |
throw DecodingError.dataCorruptedError( | |
forKey: hintKey, | |
in: container, | |
debugDescription: "unknown type hint: \(hint)" | |
) | |
} | |
self = v | |
} | |
} | |
// 以下 ドメイン固有コード | |
struct BoolParameter: Decodable { | |
let defaultValue: Bool | |
} | |
struct NumberParameter: Decodable { | |
let defaultValue: CGFloat | |
let minValue: CGFloat | |
let maxValue: CGFloat | |
} | |
enum BoolOrNumberParameter: PolymorphicDecodable { | |
case bool(BoolParameter) | |
case number(NumberParameter) | |
init?(hint: String, decoder: Decoder, container: KeyedDecodingContainer<CustomCodingKey>) throws { | |
switch hint { | |
case "bool": self = .bool(try BoolParameter(from: decoder)) | |
case "number": self = .number(try NumberParameter(from: decoder)) | |
default: return nil | |
} | |
} | |
} | |
struct Bird: Decodable { | |
let eggs: [String] | |
let destination: URL | |
} | |
enum Animal: PolymorphicDecodable { | |
case dog(name: String) | |
case cat(name: String, cuteFactor: Double) | |
case bird(Bird) | |
static var hintKeyName: String { "kind" } | |
init?(hint: String, decoder: Decoder, container: KeyedDecodingContainer<CustomCodingKey>) throws { | |
switch hint { | |
case "dog": self = .dog(name: try container.decode(String.self, forKey: "name")) | |
case "cat": self = .cat(name: try container.decode(String.self, forKey: "name"), | |
cuteFactor: try container.decode(Double.self, forKey: "cute_factor")) | |
case "bird": self = .bird(try Bird(from: decoder)) | |
default: return nil | |
} | |
} | |
} | |
struct Root: Decodable { | |
let parameter: BoolOrNumberParameter | |
let pets: [Animal] | |
} | |
let root = try JSONDecoder().decode(Root.self, from: """ | |
{ | |
"parameter": { | |
"type": "number", | |
"defaultValue": 1.5, | |
"minValue": 0.0, | |
"maxValue": 2.0 | |
}, | |
"pets": [ | |
{ "kind": "dog", "name": "pochi" }, | |
{ "kind": "cat", "name": "tama", "cute_factor": 99.99 }, | |
{ "kind": "bird", "eggs": [ "uzura", "温泉", "ホビロン" ], "destination": "https://www.youtube.com/watch?v=8kQZHYbZkLs" } | |
] | |
} | |
""".data(using: .utf8)!) | |
dump(root) | |
//▿ __lldb_expr_89.Root | |
//▿ parameter: __lldb_expr_89.BoolOrNumberParameter.number | |
// ▿ number: __lldb_expr_89.NumberParameter | |
// - defaultValue: 1.5 | |
// - minValue: 0.0 | |
// - maxValue: 2.0 | |
//▿ pets: 3 elements | |
// ▿ __lldb_expr_89.Animal.dog | |
// ▿ dog: (1 element) | |
// - name: "pochi" | |
// ▿ __lldb_expr_89.Animal.cat | |
// ▿ cat: (2 elements) | |
// - name: "tama" | |
// - cuteFactor: 99.99 | |
// ▿ __lldb_expr_89.Animal.bird | |
// ▿ bird: __lldb_expr_89.Bird | |
// ▿ eggs: 3 elements | |
// - "uzura" | |
// - "温泉" | |
// - "ホビロン" | |
// ▿ destination: https://www.youtube.com/watch?v=8kQZHYbZkLs | |
// - _url: https://www.youtube.com/watch?v=8kQZHYbZkLs #0 | |
// - super: NSObject |
This comment has been minimized.
This comment has been minimized.
※ なおJSONはあくまで例であり、
ではなく
のように、オブジェクト種別のヒントと具体的なデータはnestさせてオブジェクトごと切り分けることを強く勧めます |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
元コードから変わった点
CustomDecodedParameter
をデータごとにいちいち作らなくていいようドメイン固有の処理を切り出し、共通部分をprotocol extensionに