最近リファクタをしていて、同じオブジェクトを使ってencodeとdecodeをする際に、必要なケースがでてきたので書きました。
状況
以下のHoge
のように一部のパラメーターでdecodeはしたいが、なにかの事情でencodeしたくないがためにencode(to:)
を実装している状況です。
struct Hoge: Codable { var id: Int var name: String var status: String // これだけencodeしたくない enum CodingKeys: String, CodingKey { case id case name case status } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) } }
上のようにに書いた場合、パラメーターが増えるたびに、CodingKeys
やencode(to:)
に付け足さなければならなくなるので大変です。
Property wrapperを使った解決
Property wrapperを使ってwrapして、そちらをCodable
に準拠させることで楽に解決できます。
@propertyWrapper struct DecodeOnly<T: Decodable> { var wrappedValue: T } extension DecodeOnly: Codable { init(from decoder: Decoder) throws { self.wrappedValue = try T(from: decoder) } func encode(to encoder: Encoder) throws {} }
DecodeOnly
というwrapperをCodableにして、encodeの処理は空にしておくことで、decodeのみ行うことができます。(T
もEncodable
である必要はありません。)
ただし、このままだとvalueは空になるのですが、"status": {}
のようにkeyは残ってしまうので、KeyedEncodingContainer
の方も処理をskipするようにします。
public extension KeyedEncodingContainer { func encode<T: Decodable>(_ value: DecodeOnly<T>, forKey key: K) throws {} }
こうしておけば、上の例のHoge
は、
struct Hoge: Codable { var id: Int var name: String @DecodeOnly var status: String }
のように簡単に書くことが出来ます。
実際にJSONEncoder
でencodeしてみると、status
が含まれていないのがわかります。
let hoge = Hoge(id: 12, name: "aaa", status: "yeah") let encoded = try! JSONEncoder().encode(hoge) print(String(data: encoded, encoding: .utf8)!) // {"id":12,"name":"aaa"}
Equatableへの応用
似たようなケースで、一部のプロパティだけEquatable
で比較したくないといったケースにも対応できます。
struct Fuga: Equatable { var id: Int var name: String var comment: String // これだけ比較したくない static func == (lhs: Hoge, rhs: Hoge) -> Bool { return lhs.id == rhs.id && lhs.name == rhs.name } }
同じように、property wrapperをEquatable
に準拠させて解決します。
@propertyWrapper struct AlwaysEqual<T> { var wrappedValue: T } extension AlwaysEqual: Equatable { static func == (lhs: AlwaysEqual<T>, rhs: AlwaysEqual<T>) -> Bool { return true } }
これを使って、==
の実装を省略できます。
struct Fuga: Equatable { var id: Int var name: String @AlwaysEqual var comment: String }
let fugaA = Fuga(id: 1, name: "aaa", comment: "apple") let fugaB = Fuga(id: 1, name: "aaa", comment: "banana") print(fugaA == fugaB) // true
サンプルコード
こちらにおいてあります👇