Edited at

Codableで受け取るJSON文字列パターン集


はじめに

最近JSONSerializationによるディクショナリ型パースからDecodableへ差し替える対応をごりごりしていたので、よく受け取る想定をしたパターンをまとめてみた。


クラッシュさせないために

まず、以下のJSON文字列を受け取るとする。


response.json

{

"id": 123,
"title": "タイトル"
}

Codableでこれを受け取ろうとすると以下のように書ける。


Response.swift

struct Response: Decodable {

let id: Int
let title: String
}

CodingKeysを使う場合はこう。


Response.swift

struct Response: Decodable {

let id: Int
let title: String

private enum CodingKeys: String, CodingKey {
case id
case title
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
}
}



値null

上記のBodyクラスの書き方だとそれぞれ値がnullになるとクラッシュする。


response.json

{

"id": null,
"title": null
}

オプショナルつけるとnull値が受け取れるようになる。


Response.swift

struct Response: Decodable {

let id: Int?
let title: String?
}

CodingKeysを使う場合は、decode先の型指定にオプショナルをつけることを忘れずに。


Response.swift

struct Response: Decodable {

let id: Int?
let title: String?

private enum CodingKeys: String, CodingKey {
case id
case title
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int?.self, forKey: .id)
title = try container.decode(String?.self, forKey: .title)
}
}



JSONのキー自体が存在しない場合

CodingKeysを使って、イニシャライザを自前で定義しているときに判定漏れるとクラッシュする。


response.json

{

"id": 123
}

今回の場合、titleキーが含まれているか確認後にデコードを行うようにする。


Response.swift

struct Response: Decodable {

let id: Int?
let title: String?

private enum CodingKeys: String, CodingKey {
case id
case title
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int?.self, forKey: .id)
title = container.contains(.title) ? try container.decode(String?.self, forKey: .title) : nil
}
}



StringやIntなど、単純な型以外を受け取りたい


配列


response.json

{

"id": 123,
"titles": ["タイトル0", "タイトル1", "タイトル2"]
}

受け取る型を変えるだけ。


Response.swift

struct Response: Decodable {

let id: Int
let titles: [String]
}

CodingKeysを使う場合もデコード時の型指定を変更するだけ。


Response.swift

struct Response: Decodable {

let id: Int
let titles: [String]

private enum CodingKeys: String, CodingKey {
case id
case titles
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
titles = try container.decode([String].self, forKey: .titles)
}
}



入れ子


response.json

{

"id": 123,
"content": {
"title": "タイトル"
}
}

入れ子先の構造体分だけCodable準備。


Response.swift

struct Response: Decodable {

let id: Int
let content: Child
}

struct Child: Decodable {
let title: String
}



Response.swift

struct Response: Decodable {

let id: Int
let content: Child

private enum CodingKeys: String, CodingKey {
case id
case content
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
content = try container.decode(Child.self, forKey: .content)
}
}

struct Child: Decodable {
let title: String
}


なお、配列に構造体が入れ子になっている場合は上記Response.swiftのResponse構造体にて、Child型指定している箇所を[Child]型に差し替えれば動作する。


同階層の一部のキーだけ別の構造体に分けたい

別APIでも同じキーがくるからそれらの仕組みを使いまわしたいとか、ちょっと多すぎるから分けたいなどの場合。


response.json

{

"id": 123,
"title": "タイトル"
}


Response.swift

struct Response: Decodable {

let id: Int
let title: Title

private enum CodingKeys: String, CodingKey {
case id
}

init(from decoder: Decoder) throws {
title = try Title(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
}
}

struct Title: Decodable {
let title: String
}