##はじめに
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