まとめ
-
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
です。
convertFromSnakeCase
useDefaultKeys
custom(@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 名の指定に気をつけよう
間違っていたり、もっとベストプラクティスがあるかもしれないのでもしご存知ならばコメントなどで教えていただけるとありがたいです。