Swiftにおいて,JSONDecoder.keyDecodingStrategyに**.convertFromSnakeCase**を指定したときの挙動について,一部混乱したものがあったので,メモ.
環境
- Swift: 4.1.2
JSONDecoder.keyDecodingStrategyについて
(既にご存知の方は読み飛ばしてください)
例えば,サーバからのレスポンスが次のようなJSONだったとします.
{
"person_name": "kagemiku",
"person_age": 23
}
このJSONを素直にパースしようすると,Codable Protocolに適合させた次のような構造体を用意する必要があります.
struct Person: Codable {
let person_name: String
let person_age: Int
}
構造体のフィールド名がSwiftのコーディングスタイル(camelcase)になっておらず少しキモいです.構造体内にCodingKeysを定義することによって解決することはできますが,フィールドの数が多くなってくると,書くのも修正するのも大変めんどいです.
struct Person: Codable {
let personName: String
let personAge: Int
enum CodingKeys: String, CodingKey {
case personName = "person_name"
case personAge = "person_age"
}
}
「一つのJSONオブジェクト内で,keyがキャメルケースだったり,スネークケースだったりする」という悲しいレスポンスが帰ってくる場合は,上記の手法しかとりようがないのですが,「全部スネークケースである」ことが決まっている場合,次のように,JSONDecoderに対してオプションを与えてやることで,上記のような冗長気味な記述をせずに済みます.
struct Person: Codable {
let personName: String
let personAge: Int
}
let jsonData = """
{
"person_name": "kagemiku",
"person_age": 23
}
""".data(using: .utf8)!
let jsonDecoder = JSONDecoder()
// ↓↓↓↓↓↓↓↓
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
// ↑↑↑↑↑↑↑↑
do {
let result = try jsonDecoder.decode(Person.self, from: jsonData)
print(result)
} catch {
print(error.localizedDescription)
}
このjsonDecoder.keyDecodingStrategy
に与えている.convertFromSnakeCase
というオプションは,公式Docによると,JSONのキーに対して次のような変換を施した上で,構造体フィールドとのマッピングを行ってくれます
Capitalize each word that follows an underscore.
(アンダースコアに続く単語を大文字始まりにする)Remove all underscores that aren't at the very start or end of the string.
(初めと終わり以外にあるアンダースコアを全て取り除く)Combine the words into a single string.
(単語を一つの文字列へと連結する)
ぱっと読んで分かるようなわからないような...
なので,こちらの挙動をまとめてみました.
挙動一覧
1. JSON keyが通常のsnakecaseの場合
{
"person_name": "kagemiku",
"person_age": 23
}
次の構造体を用意することでマッピングできます
struct Person: Codable {
let personName: String
let personAge: Int
}
2. JSON keyが大文字のsnakecaseの場合
{
"PERSON_NAME": "kagemiku",
"PERSON_AGE": 23
}
- と同じくこちらでOK
struct Person: Codable {
let personName: String
let personAge: Int
}
3. JSON keyの途中で大文字だったり小文字だったりする場合
{
"peRson_nAme": "kagemiku",
"pERSON_aGE": 23
}
こちらも1. と同じ構造体でOKなようです.
struct Person: Codable {
let personName: String
let personAge: Int
}
一度JSON Keyは小文字にならされている?🤔
4. JSON keyの先頭,もしくは末尾に複数のアンダースコアがある場合
{
"__person_name": "kagemiku",
"person_age___": 23
}
先頭,もしくは末尾にあるアンダースコアの連続は,全てがそのまま残るようです
struct Person: Codable {
let __personName: String // アンダースコア2個
let personAge___: Int // アンダースコア3個
}
5. JSON keyがcamelcaseの場合
{
"personName": "kagemiku",
"personAge": 23
}
.convertFromSnakeCaseを指定している場合に,アンダースコアで結ばれていない,すなわちcamelcaseのkeyが与えられた場合,全て小文字にならされることはなく,そのまま使われるようです.
struct Person: Codable {
let personName: String
let personAge: Int
}
もう少し詳しくやると,次のようなJSONのとき,
{
"PERSON_NAME": "kagemiku",
"personAge": 23
}
こうなります.
struct Person: Codable {
let personName: String
let personAge: Int
}
PERSON_NAMEは全て大文字のsnakecaseなので,2. 3. と同様に一度小文字にならされているっぽいです.一方,personAgeの方はアンダースコアのない,camelcaseとなっているので,PERSON_NAMEのように全て小文字にならされることはなく,そのままとなっています.謎い😇
もっとやると,次のようなJSONのとき,
{
"person_name": "kagemiku",
"_personAge_": 23
}
相変わらず_personAge_
はそのままとなります.
struct Person: Codable {
let personName: String
let _personAge_: Int
}
単語の途中にアンダースコアがある場合のみ,はじめに説明した変換が施される??🤔🤔🤔
6. JSON Keyの途中に連続したアンダースコアがある
{
"person__name": "kagemiku",
"person___age": 23
}
途中にある連続したアンダースコアもきちんと全て取り除かれるようです.
struct Person: Codable {
let personName: String
let personAge: Int
}
7. JSON Keyにめっちゃアンダースコアがある
{
"person_n_a_m_e": "kagemiku",
"person_a_g_e": 23
}
これは仕様どおり.
struct Person: Codable {
let personNAME: String
let personAGE: Int
}
まとめ
- 単語の途中にアンダースコアが含まれている場合,2. に進む.そうでないなら何もせず,そのままkeyとなる
- 一旦全て小文字にならす(?)
- アンダースコアに続く単語を大文字始まりにする
- keyの途中にあるアンダースコアを全て取り除く
- 単語を一つの文字列へと連結する
この辺の挙動詳しい方いましたら,ぜひコメント or 編集リクエストお願いします🙏🙏🙏