まとめ
-
JSONDecoder.keyDecodingStrategyがconvertFromSnakeCaseの場合、Decodableを適用した struct でCodingKeysを指定する際は key 名の指定に気をつける - key 名はすでに camelCase に変換した状態で指定する
JSONDecoder と Decodable
Swift で JSON を struct に変換できる JSONDecoder 、便利ですよね。
let decoder = JSONDecoder()
try decoder.decode(Response.self, from: data)
などとして、JSON 形式であるはずの Data 型 data を Response 型に変換できます。
基本的に構造体のプロパティ名と JSON の Key 名が揃っているならば、 Response 型が単に Decodable を適用していればなんの問題もありません。
let jsonData = """
{
"id": 123,
"screenName": "mokumoku"
}
""".data(using: .utf8)!
struct Response: Decodable {
let id: Int
let screenName: String
}
let decoder = JSONDecoder()
try decoder.decode(Response.self, from: jsonData) // 成功
CodingKeys
しかしそう上手くいくことは少なく、 Swift は lowerCamelCase 文化であるのに対し API の key は snake_case で設定されていることが多いです。
そのような時は Response 型に CodingKeys を設定します。
let jsonData = """
{
"id": 123,
"screen_name": "mokumoku"
}
""".data(using: .utf8)!
struct Response: Decodable {
let id: Int
let screenName: String
private enum CodingKeys: String, CodingKey {
case id
case screenName = "screen_name"
}
}
let decoder = JSONDecoder()
try decoder.decode(Response.self, from: jsonData) // 成功
これだとどんな key 名にも対応できてよいのですが、例えばもっとたくさんプロパティがあって、一つだけ snake_case な場合は面倒です。
CodingKeys を実装する場合はすべてのプロパティを case に書き出さないとエラーになるので単純に面倒なのと実装漏れの恐れがあります。
KeyDecodingStorategy
そんな場合に便利なのが KeyDecodingStorategy です。
convertFromSnakeCaseuseDefaultKeyscustom(@escaping ([CodingKey]) -> CodingKey)
の三種類があります。
ここで convertFromSnakeCase を指定すると、自動的に JSON の key 名の snake_case を lowerCamelCase に変換してくれます。
let jsonData = """
{
"id": 123,
"screen_name": "mokumoku"
}
""".data(using: .utf8)!
struct Response: Decodable {
let id: Int
let screenName: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // ここが大事
try decoder.decode(Response.self, from: jsonData) // CodingKeys がなくても成功
便利ですね。
しかし、ここで JSON の key 名が微妙なために CodingKeys を実装しようとするとハマるポイントがあったので紹介します。(前フリが長すぎる)
KeyDecodingStrategy と CodingKeys 併用時の注意点
Twitter の API レスポンス はそのサービスの特性上、 id が UInt64 に収まらないものがあるため使い物になりません。
ではどうするかというと、 id_str という key に String で正しい値が返ってきているのでそれを利用します。
Swift 側では id: String として、これに id_str を対応させるようにします。
これをそのままやると以下のようになりますが、decode に失敗します。
let jsonData = """
{
"id": 123,
"id_str": "123",
"screen_name": "mokumoku"
}
""".data(using: .utf8)!
struct Response: Decodable {
let id: String
let screenName: String
private enum CodingKeys: String, CodingKey {
case id = "id_str"
case screenName
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
try decoder.decode(Response.self, from: jsonData) // "id_str" という key がないですというエラー
ではどうするかというと、enum の rawValue を指定するところを lowerCamelCase にしてから指定します。
private enum CodingKeys: String, CodingKey {
case id = "idStr"
case screenName
}
どうやら JSON の key 名を keyDecodingStrategy に従って変換してから、struct の CodingKeys にマッチさせているようで、ちょっと気持ち悪いのですがこうすると上手くいきました。
まとめ
-
keyDecodingStrategyとCodingKeysを併用する場合は key 名の指定に気をつけよう
間違っていたり、もっとベストプラクティスがあるかもしれないのでもしご存知ならばコメントなどで教えていただけるとありがたいです。