Codableについての公式の解説記事はこちらですが、JSONの具体例がなかったり読んでるだけだと若干わかりづらいところがあったので、自分なりに補足をつけてみたいと思います。主にCodingKeys周りを見ていきます。
具体的な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:)
が具体的にどういうことをやっているのかも最後の例でわかりました。