Iganinのブログ

日頃の開発で学んだ知見を中心に記事を書いています。

Codableに準拠したStructでネストされたプロパティをフラットにする際のTips

はじめに

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)!

環境設定

以下の環境を使用しています。

通常の方法

通常の方法を以下で記載します。 下の階層のプロパティにアクセスするために下の階層用の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経由でネストされた値にアクセスします。 この方法では通常の方法と異なり、 CodingKeyinit(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の実装量をあまり増やさずにネストされた値をフラットにすることが可能になります。