はじめに
Codable を使った JSON のパース方法についてです。
使い方
使い方は簡単! Codable
を継承させるだけ!(デコードだけしたい場合は Decodable
のみでも OK)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct Hoge: Codable { let text: String let num: Int let flag: Bool } let hogeJson = """ {"text": "hoge", "num": 3, "flag": true} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(text: "hoge", num: 3, flag: true) } catch let error { print(error.localizedDescription) } |
色々なJSON
オプショナル
下記のように JSON に num のキーが存在しない場合このままではエラーになります。
keyNotFound(CodingKeys(stringValue: "num", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"num\", intValue: nil) (\"num\").", underlyingError: nil))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Hoge: Codable { let text: String let num: Int let flag: Bool } let hogeJson = """ {"text": "hoge", "flag": true} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) } catch let error { print(error) // エラー } |
下記のように Optional
型にしてやるとパースできます。
1 2 3 4 5 |
struct Hoge: Codable { let text: String let num: Int? // オプショナル let flag: Bool } |
もしくは CodingKeys
と init
を実装して下記のようにデコードできなかったときに初期値を設定してやる方法だと Optional
型を避けることもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct Hoge: Codable { let text: String let num: Int let flag: Bool private enum CodingKeys: String, CodingKey { case text case num case flag } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) text = try values.decode(String.self, forKey: .text) num = (try? values.decode(Int.self, forKey: .num)) ?? 0 // これ flag = try values.decode(Bool.self, forKey: .flag) } } |
どちらも下記のように null の場合もパースできます。
1 |
{"text": "hoge", "num": null, "flag": true} |
プロパティ名とキー名が違う場合
下記のように JSON のキーと異なるプロパティ名にしたい場合は CodingKey
を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
struct Hoge: Codable { let hogeName: String let num: Int private enum CodingKeys: String, CodingKey { case hogeName = "hoge_name" case num } } let hogeJson = """ {"hoge_name": "hoge", "num": 10} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(hogeName: "hoge", num: 10) } catch let error { print(error) } |
スネークケースの場合は下記のように keyDecodingStrategy
設定することでもパースできます。
1 2 3 4 5 6 7 8 9 10 11 |
let hogeJson = """ {"hoge_name": "hoge", "num": 10} """ do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let hoge = try decoder.decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(hogeName: "hoge", num: 10) } catch let error { print(error.localizedDescription) } |
入れ子
Codable
のプロパティに Codable
を継承した型を利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct Hoge: Codable { let text: String let fuga: Fuga } struct Fuga: Codable { let text: String } let hogeJson = """ {"text": "hoge", "fuga": {"text": "fuga"}} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(text: "hoge", fuga: Fuga(text: "fuga")) } catch let error { print(error) } |
下記のようにリストもあつかえます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct Hoge: Codable { let text: String let fugaList: [Fuga] } struct Fuga: Codable { let text: String } let hogeJson = """ {"text": "hoge", "fugaList": [{"text": "fuga1"}, {"text": "fuga2"}]} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(text: "hoge", fugaList: [Fuga(text: "fuga1"), Fuga(text: "fuga2")]) } catch let error { print(error.localizedDescription) } |
enum
下記のように enum にパースできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
struct Hoge: Codable { let text: String let fuga: Fuga } enum Fuga: Int, Codable { case foo case piyo } let hogeJson = """ {"text": "hoge", "fuga": 1} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(text: "hoge", fuga: Fuga.piyo) } catch let error { print(error.localizedDescription) } |
特殊なパース方法
1 |
{"text": "hoge", "fuga": {"text": "fuga"}, "piyoList": [{"text": "piyo1"}, {"text": "piyo2"}]} |
上記のような JSON から下記のような構造体を生成したい場合。
1 2 3 4 5 |
struct Hoge { let text: String let fugaText: String let firstPiyoText: String } |
下記のように nestedContainer
と nestedUnkeyedContainer
を利用すればパースできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// デコードしかしないのでDecodable struct Hoge: Decodable { let text: String let fugaText: String let firstPiyoText: String private enum CodingKeys: String, CodingKey { case text case fuga case piyoList } private enum FugaKeys: String, CodingKey { case text } private enum PiyoKeys: String, CodingKey { case text } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) self.text = try values.decode(String.self, forKey: .text) // いらない情報あるのでこれでネストした値取得 let fuga = try values.nestedContainer(keyedBy: FugaKeys.self, forKey: .fuga) self.fugaText = try fuga.decode(String.self, forKey: .text) // いらない情報あるのでこれでネストした値取得 var piyoList = try values.nestedUnkeyedContainer(forKey: .piyoList) let piyo = try piyoList.nestedContainer(keyedBy: PiyoKeys.self) self.firstPiyoText = try piyo.decode(String.self, forKey: .text) } } let hogeJson = """ {"text": "hoge", "fuga": {"text": "fuga"}, "piyoList": [{"text": "piyo1"}, {"text": "piyo2"}]} """ do { let hoge = try JSONDecoder().decode(Hoge.self, from: hogeJson.data(using: .utf8)!) print(hoge) // Hoge(text: "hoge", fugaText: "fuga", firstPiyoText: "piyo1") } catch let error { print(error.localizedDescription) } |
おわりに
これでだいたいパースできるはず!
JSONDecoder.DateDecodingStrategy というのもあるらしいので Date
型にパースする場合は利用するといいのかも?
JSONExport を使えば簡単に JSON から Codable に準拠したクラスが生成できるので大量のキーが有る場合は便利です。
コメント