この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
モバイルアプリサービス部の中安です。
WebAPIなどデータを取得する際にはJSONを使用してデータを受け取ることが多いかと思います。
その際にはSwiftyJSON
などのライブラリを使用してJSONをシリアライズすることで構造体に変換するなどの処理をするかと思います。
しかし、Swift4からは新たに加わったプロトコルCodable
を使用すれば、ライブラリを使わずシンプルにそういった処理ができるようになるようです。
サンプル
よくWebAPIのJSONサンプルとして使われるlivedoor天気予報API(大阪)のデータを使用します。 今回は構造化の話だけに絞るので、ネットワーク処理の話は省きます。
このAPIで返ってくるデータのインターフェイスを元に構造体を定義しておきます(一部省略しています)。 イニシャライザも何もないデータの器だけ要しておくだけで十分です。
struct WeatherNews {
let title: String
let publicTime: String
let forecasts: [Forecast]
let location: WeatherLocation
let description: WeatherDescription
}
struct Forecast {
let dateLabel: String
let telop: String
let date: String
let temperature: TemperatureCollection
let image: WeatherImage
}
struct TemperatureCollection {
let min: Temperature?
let max: Temperature?
}
struct Temperature {
let celsius: String
let fahrenheit: String
}
struct WeatherImage {
let width: Int
let height: Int
let title: String
let url: String
}
struct WeatherLocation {
let city: String
let area: String
let prefecture: String
}
struct WeatherDescription {
let text: String
let publicTime: String
}
Codableプロトコル
それぞれの構造体をすべてCodable
プロトコルに準拠させます。
特に実装しなければならないイニシャライザもメソッドはありません。準拠することを宣言するだけです。
struct WeatherNews: Codable {
let title: String
let publicTime: String
let forecasts: [Forecast]
let location: WeatherLocation
let description: WeatherDescription
}
struct Forecast: Codable {
let dateLabel: String
let telop: String
let date: String
let temperature: TemperatureCollection
let image: WeatherImage
}
// 以下、他の構造体も同じように": Codable"を付ける
そして、JSONDecoder
クラスによってデコードをするだけです。
// jsonString = WebAPIから取ってきた生のJSON文字列とします
let weatherNews = try! JSONDecoder().decode(WeatherNews.self, from: jsonString.data(using: .utf8)!)
たったこれだけで、JSONの内容を元にしたWeatherNews
のオブジェクトが生成されます。
(※ただし、型やオプショナルなどには気をつけてください。少しでも間違えると変数weatherNews
はnilを返してしまいます)
これで、例えば下のようにSwiftyJSONを使ってプロパティひとつひとつに値を入れていく必要もなくなります。
struct WeatherImage {
let width: Int
let height: Int
let title: String
let url: String
init(json: JSON) {
width = json["width"].intValue
height = json["height"].intValue
title = json["title"].stringValue
url = json["url"].stringValue
}
}
楽になりますね。
ちなみにデータを作ったあとは、こんなふうに処理をすると天気予報っぽくなりました。
print(weatherNews.title)
weatherNews.forecasts.forEach { forecast in
print("\(forecast.dateLabel)(\(forecast.date)) => \(forecast.telop)")
}
print(weatherNews.description.text)
大阪府 大阪 の天気
今日(2017-08-09) => 晴のち曇
明日(2017-08-10) => 曇り
明後日(2017-08-11) => 曇時々晴
近畿地方は、気圧の谷の影響により曇りの所もありますが、おおむね晴れ
ています。
今夜の近畿地方は、山陰沖の前線の影響により、おおむね曇りで雨や雷雨
の所があるでしょう。
明日の近畿地方は、山陰沖の前線や低気圧の影響により、おおむね曇りで
夕方から夜のはじめ頃にかけ雨が降り、雷を伴い激しく降る所がある見込み
です。
大阪府では、明日は高温が予想され、熱中症の危険が特に高くなる見込
みです。暑さを避け、水分をこまめに補給するなど、十分な対策をとってく
ださい。
JSONへの変換
Codable
に準拠したオブジェクトは、JSONEncoder
クラスによって逆にJSON文字列に変換することもできます。
// weatherNews = 前項で作ったWeatherNewsオブジェクトとします
let data = try! JSONEncoder().encode(weatherNews)
let json = String(data: data, encoding: .utf8)!
print(json)
これを実行すると、JSON文字列が出力されるはずです。
JSONでテストデータを作ったり、JSONをパラメータにしなければならないAPI仕様などの場合に使えますね。
任意の変換をさせる場合
Codable
プロトコルの定義は、Decodable & Encodable
とされています。
デコードする際にはDecodable
のイニシャライザが走ることになります。
ですので、このイニシャライザを定義することによって任意の型などに変換することができます。
その際に使用するのはCodingKey
というプロトコルです。これをString型の列挙子と組み合わせて以下のように実装します。
// 先程までの定義
struct Temperature: Codable {
let celsius: String
let fahrenheit: String
}
// 摂氏はIntに華氏はDoubleで持っておきたい・・・となった場合の例
struct Temperature: Codable {
let celsius: Int
let fahrenheit: Double
enum Key: String, CodingKey {
case celsius, fahrenheit
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
// 一旦文字列として取得
let celsiusString = try container.decode(String.self, forKey: .celsius)
let fahrenheitString = try container.decode(String.self, forKey: .fahrenheit)
// 文字列を各々の型に変換
celsius = Int(celsiusString) ?? 0
fahrenheit = atof(fahrenheitString)
}
}
celsius
もfahrenheit
もJSON上は文字列型なので、文字列以外でデコードするとエラーになってしまいます。
なので、イニシャライザを使って文字列で取ったものを型変換してメンバ変数に入れる流れにしています。
まとめ
この仕組みを使うと、構造体がずいぶんとすっきりになるような気がします。
Alamofireなどと相性よくコード量が減るSwiftyJSONですが、さらにコードをダイエットさせられるかもしれません。