Skip to content

Instantly share code, notes, and snippets.

@takasek
Last active July 18, 2020 09:39
Show Gist options
  • Save takasek/d805b0e1a0bef7746172fc609995ed88 to your computer and use it in GitHub Desktop.
Save takasek/d805b0e1a0bef7746172fc609995ed88 to your computer and use it in GitHub Desktop.
「派生型プロパティを 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
@takasek
Copy link
Author

takasek commented Jul 18, 2020

※ なおJSONはあくまで例であり、

  • 情報に意味はありません
  • そもそもこういうレスポンス設計はおすすめしません
{
    "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" }
    ]
}

ではなく

{
    "pets": [
        { "kind": "dog", "dog": { "name": "pochi" } },
        { "kind": "cat", "cat": { "name": "tama", "cute_factor": 99.99 } },
        { "kind": "bird", "bird": { "eggs": [ "uzura", "温泉", "ホビロン" ], "destination": "https://www.youtube.com/watch?v=8kQZHYbZkLs" } }
    ]
}

のように、オブジェクト種別のヒントと具体的なデータはnestさせてオブジェクトごと切り分けることを強く勧めます

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment