この記事の内容は:
- (エンコーディング)
Codable
を使ってSwift
オブジェクトを JSON文字列にエンコードする - (デコーディング) JSON文字列を
JSONDecoder()
でデコードする - (デコーディング)
SwiftyJSON
を使って JSON文字列をデコードする
例では、仮に以下のプログラムの定義があるとしましょう
struct CatInformation {
let id: Int
let name: String
let toys: [String]
let status: CatStatus
}
struct CatStatus {
let happy: Bool
let playing: Bool
}
オブジェクトからJSON文字列を生成
我々は Swift
オブジェクトCatInformationをJSON文字列に変換したいと考えています。
以下は Swift
オブジェクトを定義したコードです:
let status = CatStatus(happy: true, playing: true)
let catInfo = CatInformation(id: 1, name: "ネコノヒー", toys: ["ペット小屋", "ぐるぐるタワー"], status: status)
構造体 Codable
を作る
プログラム構造体の定義に Codable
プロパティを追加することから始めます
struct CatInformation: Codable {
let id: Int
let name: String
let toys: [String]
let status: CatStatus
}
struct CatStatus: Codable {
let happy: Bool
let playing: Bool
}
JSONへの変換
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
let jsonData = try encoder.encode(catInfo)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString)
} catch {
print(error.localizedDescription)
}
そして、出力されたJSON文字列は以下の通りです
"{\n \"status\" : {\n \"happy\" : true,\n \"playing\" : true\n },\n \"id\" : 1,\n \"name\" : \"ネコノヒー\",\n \"toys\" : [\n \"ペット小屋\",\n \"ぐるぐるタワー\"\n ]\n}"
JSONデコーディング
この例では、以下のJSON文字列をウェブサイトのAPIコールから受け取ったとします。
{
"id": 1,
"name": "ネコノヒー",
"toys": ["ペット小屋", "ぐるぐるタワー"],
"status": {
"happy": true,
"playing": true
}
}
この文字列をSwiftでどのようにデコードしますか。この記事では、このJSON文字列をデコードする2つの方法について考えます。
- 新しい構造体を定義し、AppleのJSONDecoder()を使う
- 既存の構造体を用い、AppleのJSONDecoder()を使う
- SwiftyJSONのようなオープンソースフレームワークを使う
方法1 新しい構造体でJSONDecoder()を使用する(推奨される方法で簡単)
構造体を作成することができます。構造体はJSONファイル内のものと同じ変数名を含んでいなければなりません。例えば、JSONファイル内に id
, name
, toys
, status
があるとします。その場合、構造体の中の変数を id
, name
, toys
, status
と名付けなければなりません。
struct CatInformation: Decodable {
let id: Int
let name: String
let toys: [String]
let status: CatStatus
}
status
は入れ子構造の変数(辞書型JSON)なので、それ用の別のプログラム構造を作る必要があります。
struct CatStatus: Decodable {
let happy: Bool
let playing: Bool
}
これでJSONメッセージをデコードすることができます
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(CatInformation.self, from: jsonData)
print(catInfo)
そしてオブジェクトの配列をデコードします
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(Array<CatInformation>.self, from: jsonData)
print(catInfo)
出力データは以下の通りです
[
JSONdecode.CatInformation(catID: 1, catName: "ネコノヒー", catToys: ["ペット小屋", "ぐるぐるタワー"], catIsHappy: true, catIsPlaying: true),
JSONdecode.CatInformation(catID: 2, catName: "ムギ", catToys: ["ぬいぐるみ"], catIsHappy: true, catIsPlaying: true),
JSONdecode.CatInformation(catID: 3, catName: "レオ", catToys: [], catIsHappy: true, catIsPlaying: false)
]
上記のコードを使ってJSON文字列をデコードしてください。最適なコードのはずです。
既存の構造体に JSONDecoder
関数を使いたい方、オープンソースのソリューションを使いたい方は続きをご覧ください。
方法2 デコード可能な機能を既存の構成に追加する
上記の最初の例では、JSON変数の名前が Swift
変数に自動的にマッピングされます。ただし、名前が異なる場合は、 CodingKey
プロパティを追加できます。
以下が既存の構成です:
struct CatInformation {
let catID: Int
let catName: String
let catToys: [String]
let catIsHappy: Bool
let catIsPlaying: Bool
}
最初に、クラス/構成にデコード可能なプロパティを追加します
struct CatInformation: Decodable {
...
}
デコードキーを指定します
次に、JSON変数名を指定します。
struct CatInformation: Decodable {
...
enum CatInformationKeys: String, CodingKey {
case catID = "id"
case catName = "name"
case catToys = "toys"
case catStatus = "status"
}
...
}
これで、JSON文字列から、"happy" および "playing" プロパティが "status" 変数(ネストされた変数)内に格納されていることがわかります。したがって、 "happy" と "playing" を CodingKey の追加セットとして指定する必要があります。
struct CatInformation: Decodable {
...
enum CatStatusKeys: String, CodingKey {
case isHappy = "happy"
case isPlaying = "playing"
}
...
}
CatInformation
を初期化します
まずは初期化関数を使ってみましょう
struct CatInformation: Decodable {
...
init(from decoder: Decoder) throws {
...
}
}
init関数内:
まず、JSON文字列全体をデコードします:
let values = try decoder.container(keyedBy: CatInformationKeys.self)
次に、入れ子になっていない変数をデコードします: catID
, catName
, catToys
catID = try values.decode(Int.self, forKey: .catID)
catName = try values.decode(String.self, forKey: .catName)
catToys = try values.decode([String].self, forKey: .catToys)
次に、nestedContainer(keyedBy:forKey:)
を使用してステータスをデコードします
let status = try values.nestedContainer(keyedBy: CatStatusKeys.self, forKey: .catStatus)
catIsHappy = try status.decode(Bool.self, forKey: .isHappy)
catIsPlaying = try status.decode(Bool.self, forKey: .isPlaying)
これでinit関数のコーディングは完了です。これでJSON文字列をデコードすることができます:
これがJSON文字列です:
let singleCatString =
"""
{
"id": 1,
"name": "ネコノヒー",
"toys": ["ペット小屋", "ぐるぐるタワー"],
"status": {
"happy": true,
"playing": true
}
}
"""
これがデコード作業のコードです
let jsonData = singleCatString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(CatInformation.self, from: jsonData)
print(catInfo)
通常、JSON 文字列は、このようなオブジェクトの配列を含んでいます:
let jsonString =
"""
[
{
"id": 1,
"name": "ネコノヒー",
"toys": ["ペット小屋", "ぐるぐるタワー"],
"status": {
"happy": true,
"playing": true
}
},
{
"id": 2,
"name": "ムギ",
"toys": ["ぬいぐるみ"],
"status": {
"happy": true,
"playing": true
}
},
{
"id": 3,
"name": "レオ",
"toys": [],
"status": {
"happy": true,
"playing": false
}
},
]
"""
これで上記のJSON文字列をデコードすることができます:
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let catInfo = try! decoder.decode(Array<CatInformation>.self, from: jsonData)
print(catInfo)
以下はプログラムの出力です
[
JSONdecode.CatInformation(catID: 1, catName: "ネコノヒー", catToys: ["ペット小屋", "ぐるぐるタワー"], catIsHappy: true, catIsPlaying: true),
JSONdecode.CatInformation(catID: 2, catName: "ムギ", catToys: ["ぬいぐるみ"], catIsHappy: true, catIsPlaying: true),
JSONdecode.CatInformation(catID: 3, catName: "レオ", catToys: [], catIsHappy: true, catIsPlaying: false)
]
方法3 SwiftyJSON
SwiftyJSON
は、JSONデータのデコードを支援するためのGithub上のオープンソースフレームワークです。
インストール方法
SwiftyJSON
import SwiftyJSON
let data = jsonString.data(using: .utf8)!
let json = JSON(data)
if let catArrays = json.array {
for catInformation in catArrays {
if let catDictionary = catInformation.dictionary {
let id = catDictionary["id"]?.int
let name = catDictionary["name"]?.string
let toys = catDictionary["toys"]?.array as? [String]
//Decode the status
if let status = catDictionary["status"]?.dictionary {
let isHappy = status["happy"]?.bool
let isPlaying = status["playing"]?.bool
print("\(id), \(name), \(toys), \(isHappy), \(isPlaying)")
}
}
}
}
ここで、.int
は値を任意の整数にキャストします。.string
はオプションのStringに値をキャストします。
そして、こちらがプログラムの出力です::
Optional(1), Optional("ネコノヒー"), nil, Optional(true), Optional(true)
Optional(2), Optional("ムギ"), nil, Optional(true), Optional(true)
Optional(3), Optional("レオ"), Optional([]), Optional(true), Optional(false)