Sponsor Link
環境&対象
- macOS Monterery beta 2
- Xcode 13 beta2
JSON ファイル
JSON とは、JavaScript Object Notation の省略形です。和訳すると「JavaScript のオブジェクト記法」でしょうか。
名前に JavaScript と入っていますが、JavaScript 環境だけではなく、データを受け渡す時のフォーマットとして 広く使われるようになっています。
JSON ファイル例
{
"name": "Suzuki",
"age": 30
}
特に説明がなくとも、なんとなく意味がとれるのも、JSON ファイルの特徴です。
表されているデータは、”name” という名前のプロパティを “Suzuki” という値で持ち、”age” という名前のプロパティを 30 という値で持つことを表しています。
厳密には、プロパティの型情報は含まれていません。
対応する Swift struct
Swift 的に書くと以下のような struct とそのインスタンスに該当します。
struct Person {
var name: String
var age: Int
}
let Person = Person(name: "Suzuki", agen: 30)
ファイルデータの扱い
Swift では、ファイルのデータを Data として扱い、処理することがよくあります。
ファイルの中身が文字列の時は、一度バイナリデータを表す Data として受け取り、そこから String として復元します。
例えば、FileWrapper にはそのファイルの中身を取得する regularFileContents メソッドが用意されています。このメソッドは、Data を返します。
var regularFileContents: Data? { get }
逆に 文字列データをファイルに書き出す時向けに、String を Data に変換するためのメソッドが StringProtocol に用意されています。
func data(using encoding: String.Encoding, allowLossyConversion: Bool = false) -> Data?
ファイル処理には、FileWrapper を使いますが、Data を使って、FileWrapper を作成する initializer も用意されています。この FileWrapper をつかうことで、指定した中身を持つファイルを作ることができます。
init(regularFileWithContents contents: Data)
以下では、具体的なファイルの扱いを省略して、Data として持っている JSON を扱っていきます。
つまり、JSON ファイルから読み込んだと想定する Data から、Swift の struct/class を復元したり、struct/class を (JSON ファイルの中身になるはずの) Data へと変換します。
以下は、先の JSON データを Data にするためのコードになります。最後に付与されている “.data(using: .utf8)” が文字列を Data 化している箇所です。
static let localJSONData = """
{
"name": "Suzuki",
"age": 30
}
""".data(using: .utf8)!
}
JSON を処理するために使用するクラス、プロトコル
JSONEncoder, JSONDecoder
データを JSON に/から 変換してくれるクラスとして JSONEncoder, JSONDecoder が提供されています。
JSONEncoder, JSONDecoder が対象とするデータには、Codable であることが必要という条件があります。
Codable, Encodable, Decodable
Apple のドキュメントは、こちら と こちら と こちら。
ドキュメントを見るとわかりますが、Codable = Encodable + Decodable です。
Encodable, Decodable の名称は、それぞれ “Encode + able(できる)” と ”Decode + able(できる)” からきています。
encode は、ある特定のルールに従って、データを目的のフォーマットに変換すること。decode とは特定のフォーマットからデータを復元することです。
これらを使用して、処理していきます。
Swift の多くのタイプは、すでに Codable
Int, Double, String 等は、すでに Codable に conform しています。
ですので、Int を JSON へ保存したり、JSON から Int を復元したりすることは、簡単にできます。
ですが、通常 JSON を使って処理したい対象は、自分で定義した (もう少し複雑な構造を持つ) class や struct でしょう。
自分で定義した class/struct を Codable に conform させる
Swift で定義した class/struct は、その class/struct が保持するプロパティがすべて Codable であれば、Codable にすることができます。
Codable にする方法は、その class/struct の定義で Codable に conform していると宣言することです。
以下の Person という struct は、プロパティに String, Int を持っていますが、どちらも Codable ですので、Person も宣言に追加することで Codable にすることができます。
struct Person:Codable { // - Codable に conform
var name: String
var age: Int
}
JSON データの読み書き:シンプルなケース
JSON からの読み込み
JSONDecoder.decode を使用して、JSON を Swift の class/struct に変換します。
let localData = """
{
"name": "Suzuki",
"age": 30
}
""".data(using: .utf8)!
if let personFromJSON = try? JSONDecoder().decode(Person.self, from: localData) {
}
JSON への書き出し
JSONEncoder.encode を使用して、Swift の class/struct を JSON の Data に変換します。
let person = Person(name: "Takahashi", age: 22)
if let data = try? JSONEncoder().encode(person) {
...
}
JSON データの読み書き:異なるプロパティ名の JSON を処理したい
JSON ファイルを外部とのやりとりに使うと、class/struct のプロパティ名と一致しないケースが発生することがあります。
そのような時には、CodingKeys を定義することで、対応関係を指定することができます。
JSON フォーマット
{
"name": "Suzuki",
"age": 30
}
Swift struct
class Person:Codable {
var namae: String
var age: Int
init(name: String, age: Int) {
self.namae = name
self.age = age
}
}
JSON で “name” というキー値になっているものを Swift では、”namae” で扱いたいケースです。
読み書きいずれも、CodingKeys を class/struct で定義することでその対応関係を指定することができます。
なお、CodingKeys を定義したときは、JSON での処理対象のプロパティすべてを定義する必要があります。
class Person:Codable {
var namae: String
var age: Int
private enum CodingKeys: String, CodingKey{
// (1)
case namae = "name"
// (2)
case age
}
init(name: String, age: Int) {
self.namae = name
self.age = age
}
}
- ここで、プロパティ namae が JSON での name に対応することを定義します
- age は、そのままの処理なので、処理対象であることを定義するだけです
JSON からの読み込み
CodingKeys が正しく定義されていれば、これまで通りの読み込みで処理できます。
JSON への書き出し
書き出し処理も同じです。CodingKeys が正しく定義されていれば、これまで通りのコードで処理されます。
JSON データの読み書き:特定のプロパティを対象から外したい
データが複雑になってくると、class/struct の一部プロパティのみを JSON とやりとりしたいケースが発生することがあります。
先ほどの CodingKeys を適切に定義することで、一部のプロパティのみを JSON とのやりとりの対象にすることができます。
以下は、JSON から age 情報を取得しないようにするケースです。
class Person:Codable {
var name: String
// (1)
var age: Int = 10
// (2)
private enum CodingKeys: String, CodingKey{
case name
// case age
}
init(name: String, age: Int = 10) {
self.name = name
self.age = age
}
}
- JSON から読み込まない情報は、デフォルト値が設定される必要があります(インスタンス生成時に不定値をとることは許されません)
- CodingKeys から age を外すことで、JSONEncoder,JSONDecoder の処理対象から外れます
JSON からの読み込み
CodingKeys が正しく定義されていれば、これまで通りの読み込みで処理できます。
JSON への書き出し
書き出し処理も同じです。CodingKeys が正しく定義されていれば、これまで通りのコードで処理されます。
処理対象になっていないプロパティは、デフォルト値が適用されていることに注意する必要があります。
JSON データの読み書き:特殊な変換を実装指定
さらに複雑なケースを想定すると、JSON と Swift のモデルの関係が1:1ではない時があります。つまり、何らかの変換をして JSON を読み込みたい/書き出したいケースです。
このような時には、Codable に準拠することで提供されていたデフォルト実装を上書きすることになります。
JSON からの読み込み
Decodable から提供されるデフォルト実装を上書きすることになります。
Decodable から提供される実装
Person を Codable に conform させることで以下のようなコードがデフォルト実装として提供されます。
init(from decoder: Decoder) throws {
let myContainer = try decoder.container(keyedBy: CodingKeys.self)
self.name = try myContainer.decode(String.self, forKey: .name)
self.age = try myContainer.decode(Int.self, forKey: .age)
}
ここで、name をすべて小文字にして読み込みたいとすると、以下のような変換を init(from decoder:) で行うことになります。
init(from decoder: Decoder) throws {
let myContainer = try decoder.container(keyedBy: CodingKeys.self)
let jsonName = try myContainer.decode(String.self, forKey: .name)
self.name = jsonName.lowercased()
self.age = try myContainer.decode(Int.self, forKey: .age)
}
JSON への書き出し
Encodable から提供されるデフォルト実装を上書きすることになります。
Encodable から提供される実装
Person を Codable に conform させることで以下のようなコードがデフォルト実装として提供されます。
func encode(to encoder: Encoder) throws {
try name.encode(to: encoder)
try age.encode(to: encoder)
}
ここで、例えば name をすべて大文字にして JSON に保存したいとすると以下のような変換を行うことになります。
func encode(to encoder: Encoder) throws {
let uppercasedName = name.uppercased()
try uppercasedName.encode(to: encoder)
try age.encode(to: encoder)
}
この方法を使うと、ここで説明している変換だけではなく、細かい変換ルールを制御することができます。
まとめ:Swift での JSON データの扱い方
- Codable = Encodable + Decodable がポイントとなるプロトコル
- 多くの基本型が Codable 対応している
- Codable な型のプロパティを持つ class/struct は、Codable になる
- CodingKeys を使うことで、キー値のカスタマイズが可能
- encode(to encoder:) と init(from decoder:) を使用することで、こまかなカスタマイズが可能
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Swift 学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Swift ポケットリファレンス
Swift を学んでも、プログラミング言語の文法を全て記憶しておくことは無理なので、ちょっとした文法の確認をするために、リファレンス本を手元に置いておくと便利です。
Swift4 までしか対応していないので、相違点を理解して参照する必要があります。
Sponsor Link