はじめに
Codableに準拠したStructやClassにおいて、ネストされた値をDecodeの際にフラットにしたいとします。
例えば以下のようなJSONに対して、下の階層の値であるaddress.nameを上位の階層のaddressNameとして扱いたい場合などです。
一般的にはこのような場合は、 nestedContainer
を使用し、 init(from decoder: Decoder)
を使用しますが、ちょっとしたテクニックを使用するとこの実装が不要となります。
本記事ではその方法についてのTipsをご紹介します。
let jsonData = """ { "first_name": "Taro", "last_name": "Tanaka", "age": "30", "gender": "male", "address": { "code": 13, "name": "Tokyo" } } """.data(using: .utf8)!
環境設定
以下の環境を使用しています。
- Xcode10.0
- iOS 12.0
通常の方法
通常の方法を以下で記載します。
下の階層のプロパティにアクセスするために下の階層用のCodingKeyを定義します。
それを使用し、 nestedContainer
経由でプロパティの値を決めます。
この方法の場合、CodingKey
の定義とinit(from decoder: Decoder)
の実装が必要になります。
struct Person: Decodable { let firstName: String let lastName: String let age: String let gender: String let addressName: String private enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case age case gender case address } // 下の階層のプロパティアクセス用にCodingKeyを定義 private enum AddressCodingKeys: String, CodingKey { case code case name } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) firstName = try values.decode(String.self, forKey: .firstName) lastName = try values.decode(String.self, forKey: .lastName) age = try values.decode(String.self, forKey: .age) gender = try values.decode(String.self, forKey: .gender) // 下の階層のプロパティにアクセスするために nestedContainerを使用し、Containerを作成 let addressValue = try values.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address) // nestedContainer経由で下の階層の値を取得、自身のプロパティにセットします addressName = try addressValue.decode(String.self, forKey: .name) } }
本記事での方法
Struct 内部にネストされた値のパース用のstructを定義し、パースされたstructをprivateで保持します。
プロパティから値を取得する場合は、 Computed Property経由でネストされた値にアクセスします。
この方法では通常の方法と異なり、 CodingKey
と init(from decoder: Decoder
の実装が不要となります。
struct Person: Decodable { let firstName: String let lastName: String let age: String let gender: String // Computed Propertyを使用し、privateのプロパティから値を取得し返却する var addressName: String { return address.name } // パースにのみ使用するため privateで定義 private let address: Address // AddressをパースするためにStruct内限定のStructを定義 private struct Address: Decodable { let code: String let name: String } }
まとめ
場合により使い分けるべきだと思いますが、本記事の方法によってstructの実装量をあまり増やさずにネストされた値をフラットにすることが可能になります。