14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DecodableでkeyDecodingStrategyとCodingKeyを併用するさいに気をつけるべきこと

Posted at

##はじめに
SwiftでJSONのパースを行う際はCodableが非常に便利です。
サーバとの通信のレスポンスではSnake Caseが使用されることが多く、 iOSアプリ内ではCamel Caseが使用されることが多いです。それらの相互の変換がJSONDecoderのkeyDecodingStrategyを使用すると簡単に行えますが、 CodingKeyと併用した際に少しはまったポイントがあったため共有します。

なお、keyDecodingStrategyに関しましては別記事に記載していますので、そちらをご確認ください。

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

  • macOS Mojave Version 10.14
  • Xcode Version 10.0.0

##発生事象例

以下のように、サーバからの通信では姓に該当する名称がfamily_nameで返ってきますが、アプリ内ではlastNameとして使用したいとします。この場合、key名が異なっているため基本的にはCodingKeyを使用してKeyの対応関係をつけるかと思います。

import Foundation

let jsonData = """
{
    "first_name": "Taro",
    "family_name": "Tanaka"
}
""".data(using: .utf8)!

struct User: Decodable {
    let firstName: String
    let lastName: String
    
    enum CodingKeys: String, CodingKey {
        case firstName
        case lastName = "family_name"
    }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
    let user = try decoder.decode(User.self, from: jsonData)
    print("first name: \(user.firstName), last name: \(user.lastName)")
} catch let error {
    print(error)
}

一見うまくいきそうですが、このコードを実行すると以下のエラーが表示されます。
family_nameに該当する keyがないというエラーです。

出力結果
keyNotFound(CodingKeys(stringValue: "family_name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"family_name\", intValue: nil) (\"family_name\").", underlyingError: nil))

##原因と対応
原因はJSONDecoderのkeyDecodingStrategyとして.convertFromSnakeCaseを使用しながら、 CodingKeyでの対応関係にSnake Caseを使用していることです。具体的には下記の箇所です。

struct User: Decodable {
    let firstName: String
    let lastName: String
    
    enum CodingKeys: String, CodingKey {
        case firstName
         // .convertFromSnakeCaseを使用しながら、
         //  Codingkeyでの対応づけにSnake Caseをそのまま使用している
        case lastName = "family_name"
    }
}

元のSwiftのソースコードをまだ追えていないので、 なぜこの挙動になるかの把握ができていませんが、 keyDecodingStrategyで.convertFromSnakeCaseを使用する際は、CodingKeyでの対応づけではJSONのkey名をSnake Caseのまま書くのではなく、Camel Caseにしなければいけないようです。以下のコードにしたところ正常にパースできました。

import Foundation

let jsonData = """
{
    "first_name": "Taro",
    "family_name": "Tanaka"
}
""".data(using: .utf8)!

struct User: Decodable {
    let firstName: String
    let lastName: String
    
    enum CodingKeys: String, CodingKey {
        case firstName
        case lastName = "familyName" // CodingKeyの対応付けをCamelCaseで行う
    }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
    let user = try decoder.decode(User.self, from: jsonData)
    print("first name: \(user.firstName), last name: \(user.lastName)")
} catch let error {
    print(error)
}
出力結果
first name: Taro, last name: Tanaka

##まとめ
JSONのデコードを通信クラスないで共通して行なっている場合など、デコード処理が隠蔽されているような場合に気づきにくいところかなと思いました。keyDecodingStrategyとCodingKeyを併用する際は気をつける必要がありそうです。
なお、サンプルコードは以下にあげていますのでよければご参照ください。
https://github.com/HironobuIga/Samples/tree/master/01_iOS/20181208/CodingKey_keyDecodingStrategy_sample.playground

14
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?