BambooHero

iOSアプリ開発と株式投資をメインに色々書きます

Swift Codableの公式解説をちょっと補足

f:id:bamboohero:20210522022335p:plain

Codableについての公式の解説記事はこちらですが、JSONの具体例がなかったり読んでるだけだと若干わかりづらいところがあったので、自分なりに補足をつけてみたいと思います。主にCodingKeys周りを見ていきます。

developer.apple.com



具体的なJSONで理解する

解説記事のコードをまとめるとこんな感じです。デコード部分に絞ってます。

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }

    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)

        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}


解説記事には出てこないので分かりづらかったのですが、CoordinateはこういうJSONに対応しています。

{
    "latitude": 1.1,
    "longitude": 2.2,
    "additionalInfo": {
        "elevation": 3.3
    }
}


デコードするコードはこんな感じですね。

let json = """
{
    "latitude": 1.1,
    "longitude": 2.2,
    "additionalInfo": {
        "elevation": 3.3
    }
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// 出力結果
// Coordinate(latitude: 1.1, longitude: 2.2)


CodingKeysという名前でなければいけないのか?

解説にはこのような記載があります。

Codable types can declare a special nested enumeration named CodingKeys that conforms to the CodingKey protocol.

上記の例でもCoordinate内部でCodingKeysというenumを宣言していますね。この名前を変えたらどうなるか試してみたいと思います。

まずはCodingKeysという名前で宣言した例です。

struct Coordinate: Decodable {
    var latitude: Double
    var longitude: Double

    enum CodingKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// 結果
// Coordinate(latitude: 1.1, longitude: 2.2)


次に、CodingKeysをHogeKeysに変えてみます。すると、latitudeというキーが無いと怒られてしまいました。HogeKeysでは当然ながら認識されないようです。

struct Coordinate: Decodable {
    var latitude: Double
    var longitude: Double

    enum HogeKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)

// エラー
// __lldb_expr_14/MyPlayground.playground:37: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "latitude", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"latitude\", intValue: nil) (\"latitude\").", underlyingError: nil))


HogeKeysという名前にする場合は、init(from:)を実装する必要があります。

struct Coordinate {
    var latitude: Double
    var longitude: Double

    enum HogeKeys: String, CodingKey {
        case latitude = "latitude_test"
        case longitude
    }
}

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: HogeKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
    }
}

let json = """
{
    "latitude_test": 1.1,
    "longitude": 2.2,
}
"""

let decoder = JSONDecoder()
let coordinate = try! decoder.decode(Coordinate.self, from: json.data(using: .utf8)!)
print(coordinate)


つまり、CodingKeysという名前にすることで、init(from:)を自動で実装してくれるんですね。さらに、init(from:)が具体的にどういうことをやっているのかも最後の例でわかりました。

まとめ

ざっくりですが、Codableについての公式の解説記事の補足をしてみました。

Appleのドキュメントは見やすくて内容も充実していますが、実際に開発するとなると細かいところはドキュメント読むだけではわからないこともあるので、こうやって色々試してみると勉強になりますね。