Swift 4 で Google Books APIs 経由で書籍を検索する

以前の記事の時に書いたコードの JSON パースするところのリファクタリングをしなければと思い立ったのが運の尽き。理解するまで時間かかった、、

ひとまず実装したこととしては、Google Books APIsを使ってHTTP経由でJSONのデータを受け取ってパースして一覧に表示すると...

Google Books API へ問い合わせる URL の構成については以前の記事をベースに。

Swift 4 から Codable を使って JSON の構成を定義した後に JSONDecoder().decode 経由で JSON の String を渡すと自動で良きに計らってくれます。

大まかな流れは

  1. 事前準備
  2. JSON データのダウンロード
  3. JSON をデコード
  4. デコードしたデータにアクセス

1. 事前準備
ひとまずブラウザで JSON データを表示。以下サンプルURL。

https://www.googleapis.com/books/v1/volumes?q=intitle:%E3%83%80%E3%83%B3%E3%82%B8%E3%83%A7%E3%83%B3%E9%A3%AF&Country=JP

そうすると以下の様な JSON データを取得できると思います。

{
 "kind": "books#volumes",
 "totalItems": 4,
 "items": [
  {
   "kind": "books#volume",
   "id": "YkMurgEACAAJ",
   "etag": "eFXppQDWdzs",
   "selfLink": "https://www.googleapis.com/books/v1/volumes/YkMurgEACAAJ",
   "volumeInfo": {
    "title": "ダンジョン飯 1",
    "subtitle": "",
    "authors": [
     "九井諒子"
    ],
    "publishedDate": "2015-01",
    "description": "待ってろドラゴン、ステーキにしてやる!",
    "industryIdentifiers": [
     {
      "type": "ISBN_10",
      "identifier": "4047301531"
     },
     {
      "type": "ISBN_13",
      "identifier": "9784047301535"
     }
    ],
    "readingModes": {
     "text": false,
     "image": false
    },
    "pageCount": 191,
    "printType": "BOOK",
    "averageRating": 4.0,
    "ratingsCount": 1,
    "allowAnonLogging": false,
=====一部割愛&中略=====

ここで階層構造を見てコードに落とし込みます。

struct TopTier : Codable {
    var kind: String
    var totalItems: Int  // " " (ダブルクォート)で囲われていない数値なので Int
    var items: [Item]    // JSON 上で [ ] (ブラケット)で囲われているので配列にする
}
struct Item: Codable {
    var kind: String
    var id: String
    var etag: String
    var selfLink: String
    var volumeInfo: VolumeInfo   // 上記 Item と同じく構造体で入れ子になっているので別途定義
    // 中略
}
struct VolumeInfo: Codable {
    var title: String
    var subtitle: String?    // 検索によってそもそも値が存在しない可能性がある場合、nil はオプショナル
    var authors: Array?      // 配列&文字列なので Array ついでに検索によって無い場合があるのでオプショナル
    // 中略
}

その他、true / false の場合は Bool で定義。

この辺りは JSON 表記方法に沿って型を指定する。
参照: JavaScript Object Notation (Wikipedia)

JSONで表現するデータ型は以下の通りで、これらを組み合わせてデータを記述する。true, false, nullなどは全て小文字でなくてはならない。

  • 数値(整数浮動小数点数
  • 文字列(バックスラッシュによるエスケープシーケンス記法を含む、ダブルクォーテーションでくくった文字列)
  • 真偽値(truefalse
  • 配列(データのシーケンス)
  • オブジェクト(順序づけされていないキーと値のペアの集まり。JSONでは連想配列と等価)
  • null

定義が重複すると以下の様なエラーで怒られます。
Duplicate definition of enum element
Invalid redeclaration of ‘変数’

2. JSON データのダウンロード
他のもっとしっかり書いてあるサイトを参照ください。

3. JSON データのパース
以下 responseData がダウンロードした Data 型のデータ。

guard let jsonString = String(data: responseData, encoding: .utf8) else {
    return
}
let json = try! JSONDecoder().decode(TopTier.self, from: jsonString.data(using: .utf8)!)

上記 TopTier が最初に定義した Codable な構造体で、.self を添えてデコーダーに指定する。
try! にしているのは、パースに失敗した場合に強制で落としてデバッグしやすくするため。デバッグ後は適宜エラー処理なり try? なりで良いかと思います。

例として、以下は description が構造体に定義されているけど description なんて無いよエラー。この場合はオプショナル(?付き)に指定すれば大丈夫と思います。

keyNotFound(VolumeInfo.(CodingKeys in _1E4CD3D77FD19170D299FEBAE8E64BFC).description, Swift.DecodingError.Context(codingPath: [TopTier.(CodingKeys in _1E4CD3D77FD19170D299FEBAE8E64BFC).items, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)),Item.(CodingKeys in _1E4CD3D77FD19170D299FEBAE8E64BFC).volumeInfo], debugDescription: "No value associated with key description (\"description\").", underlyingError: nil))

修正例:

var description: String?

4. デコードしたデータにアクセス

json.items[0].volumeInfo.title

などで取得できます。多分。

フォローする