Codableのkeyとプロパティの対応関係、Genericを使えば外から動的に注入できそうだなーと思ってやってみた。
こんなかんじ
CodingKeyMapper.swift
/// CodingKeyのマッピングを動的に切り替える
protocol CodingKeyMapper {
static var mapper: [String: S<Self>.CodingKeys] { get }
}
extension CodingKeyMapper {
static var mapper: [String: S<Self>.CodingKeys] { return [:] }
static func key(_ stringValue: String) -> S<Self>.CodingKeys? {
return mapper[stringValue]
}
static func stringValue(_ key: S<Self>.CodingKeys) -> String {
return mapper.first { $0.value == key }?.key ?? String(describing: key)
}
}
CodingKeyMapperImpl.swift
///Mapperの実体にはdictを定義するだけ
struct Pattern1: CodingKeyMapper {
static let mapper: [String: S<Pattern1>.CodingKeys] = [
"i_another": .i,
"s_another": .s,
]
}
/// 欠けたkey(👇では .s ) は、String(describing: key) として扱われる
struct Pattern2: CodingKeyMapper {
static let mapper: [String: S<Pattern2>.CodingKeys] = [
"i_yet_another": .i,
]
}
/// key名の変更がないなら何も書く必要がない
struct Pattern0: CodingKeyMapper {}
Execution.swift
struct S<Mapper: CodingKeyMapper>: Codable {
let i: Int
let s: String
enum CodingKeys: CodingKey {
case i
case s
init?(stringValue: String) {
guard let k = Mapper.key(stringValue) else { return nil }
self = k
}
var stringValue: String {
return Mapper.stringValue(self)
}
}
}
import Foundation
let data = """
{
"i": 1,
"s": "デフォルトのsですよ",
"i_another": 10,
"s_another": "別のsですよ",
"i_yet_another": 999,
}
""".data(using: .utf8)!
let decoder: JSONDecoder = JSONDecoder()
let s0 = try! decoder.decode(S<Pattern0>.self, from: data)
let s1 = try! decoder.decode(S<Pattern1>.self, from: data)
let s2 = try! decoder.decode(S<Pattern2>.self, from: data)
dump([s0, s1, s2])
実行結果
▿ 3 elements
▿ __lldb_expr_26.S<__lldb_expr_26.Pattern0>
- i: 1
- s: "デフォルトのsですよ"
▿ __lldb_expr_26.S<__lldb_expr_26.Pattern1>
- i: 10
- s: "別のsですよ"
▿ __lldb_expr_26.S<__lldb_expr_26.Pattern2>
- i: 999
- s: "デフォルトのsですよ"
思ったこと
一応できたけど、Genericな型なの気になる…。
Decoder.decode
の引数で渡せればいいんですけどね。