Codableを使ったJSON変換について解説します。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.2.4
【iOS】13.6
【macOS】Catalina バージョン 10.15.5
Codableとは?
インスタンス(オブジェクト)情報を、他の形式にデータ変換するプロトコルです。
ファイルに格納したり、ネットワークで送受信したりするためにバイト列や文字列に変換する際に使用します。
使用するにはデータ変換に使う構造体の型がCodableプロトコルに準拠している必要があります。
このプロトコルを利用してSwiftオブジェクト←→JSON形式の変換が可能です。
JSONとは?
テキストベースのデータ交換用フォーマットです。
軽量で読みやすく、多くのプログラミング言語でサポートされているのが特徴です。
扱える型は、文字列、数値、null値、boolean(true/false)、配列、オブジェクト。
文字コードはutf-8固定です。
基本的な構造体のJSON変換
基本的な構造体オブジェクトとJSON形式を相互変換する例を紹介します。
(※構造体の代わりにクラスを使用した場合も同様です)
まずは全体ソース。
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 |
struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ } /// ①初期オブジェクト生成 let originalObject = Employee(code: "001", name: "山田", age: 45, absence: false) /// ②JSONへ変換 let encoder = JSONEncoder() guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() guard let employee: Employee = try? decoder.decode(Employee.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤最終データ確認 print("***** 最終データ確認 *****") print(employee) |
対象の構造体
従業員情報を保持する構造体です。
Codableを使う為の条件として、Codableプロトコルに準拠している必要があります。
1 2 3 4 5 6 7 8 |
struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ } |
オブジェクトをJSONへ変換する
JSONEncoder()のインスタンスメソッド.encode()で対象のオブジェクトをJSON形式に変換します。
1 2 3 4 5 6 7 |
/// ②JSONへ変換 let encoder = JSONEncoder() guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } |
このメソッドは例外をスローする可能性があるので、tryを付けて呼び出します。
この例ではtry?を使っているので、例外自身は無視されますが、戻り値がnullになりますのでその場合はfatalError()が発生します。
JSONデータを確認する
確認の為、変換後のJSONデータ(jsonValue)を表示しているコードです。
1 2 3 4 5 |
/// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) |
jsonValueはData型の為、String(bytes:,encoding:)で文字列に変換してからprintしています。
encodingにはJSONの文字コードであるutf-8を指定します。
実際の出力結果がこちらです。
1 2 3 4 |
***** JSONデータ確認 ***** {"age":45,"code":"001","name":"山田","absence":false} |
JSONの整形
次のように、JSONEncoder()インスタンスのプロパティoutputFormattingに.prettyPrintedを指定すると、出力結果を人の目に見やすいように整形できます。
1 2 3 4 5 6 7 8 |
/// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted // 出力を整形 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } |
出力結果はこのように変わります。
1 2 3 4 5 6 7 8 9 |
***** JSONデータ確認 ***** { "age" : 45, "code" : "001", "name" : "山田", "absence" : false } |
JSONからオブジェクトに変換
先程とは逆の変換処理です。
1 2 3 4 5 6 7 |
/// ④JSONから変換 let decoder = JSONDecoder() guard let employee: Employee = try? decoder.decode(Employee.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } |
JSONEncoder()の代わりにJSONDecoder()を、.encode()の代わりに.decode()を使用します。
.decodeの第一引数には、"構造体名.self"を指定します。
第二引数には、変換元のjsonValue(Data型)を指定します。
最終データ確認
確認の為、JSONから変換されたオブジェクト(employee)を表示しているコードです。
1 2 3 4 |
print("***** 最終データ確認 *****") print(employee) |
出力結果はこちら。
1 2 3 4 |
***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false) |
配列のJSON変換
先程のサンプルを、オブジェクトの配列に変更してみます。
初期オブジェクト生成
変換元のオブジェクトを、このように配列に変更します。
JSONへの変換処理には特に変更はありません。
1 2 3 4 5 6 7 8 |
/// ①初期オブジェクト生成 let originalObject = [ Employee(code: "001", name: "山田", age: 45, absence: false), Employee(code: "002", name: "鈴木", age: 30, absence: true), Employee(code: "003", name: "佐藤", age: 25, absence: false) ] |
JSONからオブジェクトに変換
受け取るオブジェクトemployeesは配列型[Employee]になります。
.decode()メソッドの第一引数の型も、配列型"[Employee].self"に変更します。
1 2 3 4 5 6 7 |
/// ④JSONから変換 let decoder = JSONDecoder() guard let employees: [Employee] = try? decoder.decode([Employee].self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } |
全体ソースと実行結果
こちらが全体ソースです。
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 |
struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ } /// ①初期オブジェクト生成 let originalObject = [ Employee(code: "001", name: "山田", age: 45, absence: false), Employee(code: "002", name: "鈴木", age: 30, absence: true), Employee(code: "003", name: "佐藤", age: 25, absence: false) ] /// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted // 出力を整形 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() guard let employees: [Employee] = try? decoder.decode([Employee].self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤データ確認 print("***** 最終データ確認 *****") for employee in employees { print(employee) } |
出力結果
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 |
***** JSONデータ確認 ***** [ { "age" : 45, "code" : "001", "name" : "山田", "absence" : false }, { "age" : 30, "code" : "002", "name" : "鈴木", "absence" : true }, { "age" : 25, "code" : "003", "name" : "佐藤", "absence" : false } ] ***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false) Employee(code: "002", name: "鈴木", age: 30, absence: true) Employee(code: "003", name: "佐藤", age: 25, absence: false) |
複雑な構造体のJSON変換
オブジェクトのプロパティがさらにオブジェクトの配列になっているような複雑なデータもJSON変換可能です。
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 |
struct Section: Codable { var name: String // 部署名 var member: [Employee] // 従業員リスト struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ } } /// ①初期オブジェクト生成 let originalObject = Section(name: "営業部", member: [ Section.Employee(code: "001", name: "山田", age: 45, absence: false), Section.Employee(code: "002", name: "鈴木", age: 30, absence: true), Section.Employee(code: "003", name: "佐藤", age: 25, absence: false) ]) /// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted // 出力を整形 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() guard let section: Section = try? decoder.decode(Section.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤データ確認 print("***** 最終データ確認 *****") print(section.name) for employee in section.member { print(employee) } |
出力結果
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 |
***** JSONデータ確認 ***** { "name" : "営業部", "member" : [ { "age" : 45, "code" : "001", "name" : "山田", "absence" : false }, { "age" : 30, "code" : "002", "name" : "鈴木", "absence" : true }, { "age" : 25, "code" : "003", "name" : "佐藤", "absence" : false } ] } ***** 最終データ確認 ***** 営業部 Employee(code: "001", name: "山田", age: 45, absence: false) Employee(code: "002", name: "鈴木", age: 30, absence: true) Employee(code: "003", name: "佐藤", age: 25, absence: false) |
オプショナル型の指定
従業員情報のプロパティで「役職」のように、人によっては値を持たないケースがあります。
その場合は構造体の定義でオプショナル型を指定すると良いです。
値がnilのプロパティをJSON変換するとそのキー自体が無くなります。
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 |
struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ var position: String? // 役職 } /// ①初期オブジェクト生成 let originalObject = [ Employee(code: "001", name: "山田", age: 45, absence: false, position: "部長"), Employee(code: "002", name: "鈴木", age: 30, absence: true), Employee(code: "003", name: "佐藤", age: 25, absence: false) ] /// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted // 出力を整形 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() guard let employees: [Employee] = try? decoder.decode([Employee].self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤データ確認 print("***** 最終データ確認 *****") for employee in employees { print(employee) } |
出力結果
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 |
***** JSONデータ確認 ***** [ { "age" : 45, "code" : "001", "name" : "山田", "position" : "部長", "absence" : false }, { "age" : 30, "code" : "002", "name" : "鈴木", "absence" : true }, { "age" : 25, "code" : "003", "name" : "佐藤", "absence" : false } ] ***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false, position: Optional("部長")) Employee(code: "002", name: "鈴木", age: 30, absence: true, position: nil) Employee(code: "003", name: "佐藤", age: 25, absence: false, position: nil) |
日付のフォーマット変換
JSONで日付データは文字列型を使用しますが、変換フォーマットを指定する事でSwiftのDate型と直接変換ができます。
以下は社員情報に入社日dateOhHireを設けて、標準的なフォーマットの.iso8601(ISO8601形式)を指定した例です。
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 |
struct Employee: Codable { var code: String // 社員コード var name: String // 名前 var age: Int // 年齢 var absence: Bool // 休暇中フラグ var dateOfHire: Date // 入社日 } /// ①初期オブジェクト生成 let originalObject = Employee(code: "001", name: "山田", age: 45, absence: false, dateOfHire: Date()) /// ②JSONへ変換 let encoder = JSONEncoder() let dateFormatter = DateFormatter() encoder.outputFormatting = .prettyPrinted encoder.dateEncodingStrategy = .iso8601 // Date変換フォーマットの指定 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 // Date変換フォーマットの指定 guard let employee: Employee = try? decoder.decode(Employee.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤最終データ確認 print("***** 最終データ確認 *****") print(employee) |
出力結果
1 2 3 4 5 6 7 8 9 10 11 12 |
***** JSONデータ確認 ***** { "age" : 45, "code" : "001", "name" : "山田", "dateOfHire" : "2020-07-23T03:53:51Z", "absence" : false } ***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false, dateOfHire: 2020-07-23 03:53:51 +0000) |
日付フォーマットをカスタマイズする
次のようにDateFormatterを利用して、日付フォーマットをカスタマイズできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/// 日付フォーマット let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy年M月d日" /// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted encoder.dateEncodingStrategy = .formatted(dateFormatter) // Date変換フォーマットの指定 guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ④JSONから変換 let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(dateFormatter) // Date変換フォーマットの指定 guard let employee: Employee = try? decoder.decode(Employee.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } |
出力結果
1 2 3 4 5 6 7 8 9 10 11 12 |
***** JSONデータ確認 ***** { "age" : 45, "code" : "001", "name" : "山田", "dateOfHire" : "2020年7月23日", "absence" : false } ***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false, dateOfHire: 2020-07-22 15:00:00 +0000) |
キーマッピングの変更
Swiftのプロパティ名と、JSONのキー名が完全には一致しない場合は、キーマッピングの指定が可能です。
次のように対象の構造体でCodingKeysというenum型を追加して、case名にプロパティ名を、「= "キー"」でJSONのキー名を指定します。
キー名が無い場合は、同名で変換されます。
caseが無いプロパティは変換の対象外になりますが、decodeで使う場合はデフォルト値の設定が必要です。
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 |
struct Employee: Codable { var code: String var name: String var age: Int var absence: Bool = false // 変換対象外(デフォルト値を設定する) private enum CodingKeys: String, CodingKey { case code = "id" case name case age } } /// ①初期オブジェクト生成 let originalObject = Employee(code: "001", name: "山田", age: 45, absence: false) /// ②JSONへ変換 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted guard let jsonValue = try? encoder.encode(originalObject) else { fatalError("Failed to encode to JSON.") } /// ③JSONデータ確認 print("***** JSONデータ確認 *****") print(String(bytes: jsonValue, encoding: .utf8)!) /// ④JSONから変換 let decoder = JSONDecoder() guard let employee: Employee = try? decoder.decode(Employee.self, from: jsonValue) else { fatalError("Failed to decode from JSON.") } /// ⑤最終データ確認 print("***** 最終データ確認 *****") print(employee) |
出力結果
1 2 3 4 5 6 7 8 9 10 |
***** JSONデータ確認 ***** { "id" : "001", "name" : "山田", "age" : 45 } ***** 最終データ確認 ***** Employee(code: "001", name: "山田", age: 45, absence: false) |