12
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 3 years have passed since last update.

SwiftアプリケーションにおいてJSON文字列で作業をする(エンコーディングとデコーディング)

Last updated at Posted at 2020-08-19

この記事の内容は:

  • (エンコーディング)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つの方法について考えます。

  1. 新しい構造体を定義し、AppleのJSONDecoder()を使う
  2. 既存の構造体を用い、AppleのJSONDecoder()を使う
  3. 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)

:relaxed: Twitter @MszPro

:sunny: 私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。

12
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
12
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?