15
8

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.

JSONDecoderで.convertFromSnakeCase指定時の挙動一覧

Last updated at Posted at 2018-09-16

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のキーに対して次のような変換を施した上で,構造体フィールドとのマッピングを行ってくれます

  1. Capitalize each word that follows an underscore.
    (アンダースコアに続く単語を大文字始まりにする)

  2. Remove all underscores that aren't at the very start or end of the string.
    (初めと終わり以外にあるアンダースコアを全て取り除く)

  3. 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
}
  1. と同じくこちらで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
}

まとめ

  1. 単語の途中にアンダースコアが含まれている場合,2. に進む.そうでないなら何もせず,そのままkeyとなる
  2. 一旦全て小文字にならす(?)
  3. アンダースコアに続く単語を大文字始まりにする
  4. keyの途中にあるアンダースコアを全て取り除く
  5. 単語を一つの文字列へと連結する

この辺の挙動詳しい方いましたら,ぜひコメント or 編集リクエストお願いします🙏🙏🙏

参考サイト

15
8
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
15
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?