[解決済み] Swift 3でJSONを正しくパースする
質問
私はJSON応答をフェッチし、変数に結果を格納しようとしています。Xcode 8 の GM バージョンがリリースされるまで、私はこのコードのバージョンを持っていた Swift の以前のリリースで動作します。私は、StackOverflow上のいくつかの同様の投稿を見ていました。 Swift 2 JSON を解析する - 'AnyObject' 型の値を添え字で指定できません。 と Swift 3でのJSONのパース .
しかし、そこで伝えられたアイデアは、このシナリオでは適用されないようです。
Swift 3 で JSON 応答を正しく解析する方法は? Swift 3 で JSON が読み取られる方法で何かが変わったのでしょうか?
以下は問題のコードです(プレイグラウンドで実行することができます)。
import Cocoa
let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
if let url = NSURL(string: url) {
if let data = try? Data(contentsOf: url as URL) {
do {
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
//Store response in NSDictionary for easy access
let dict = parsedData as? NSDictionary
let currentConditions = "\(dict!["currently"]!)"
//This produces an error, Type 'Any' has no subscript members
let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue
//Display all current conditions from API
print(currentConditions)
//Output the current temperature in Fahrenheit
print(currentTemperatureF)
}
//else throw an error detailing what went wrong
catch let error as NSError {
print("Details of JSON parsing error:\n \(error)")
}
}
}
編集します。
の後にAPIを呼び出した結果のサンプルは以下の通りです。
print(currentConditions)
["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
どのように解決するのですか?
まず最初に
リモートURLから同期的にデータを読み込まない
のような非同期メソッドを使用します。
URLSession
.
'Any' には添え字のメンバがありません。
が発生するのは、コンパイラが中間オブジェクトがどのような型であるかを知らないためです(例えば
currently
で
["currently"]!["temperature"]
のような Foundation コレクションタイプを使用しているためです。
NSDictionary
のようなFoundationのコレクション型を使っているので、コンパイラはその型について全く知りません。
さらにSwift 3では、コンパイラーに すべて の型についてコンパイラに知らせる必要があります。
JSONシリアライズの結果を実際の型にキャストする必要があります。
このコードでは
URLSession
と
だけ
Swift ネイティブタイプ
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let currentConditions = parsedData["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}
}.resume()
の全てのキーと値のペアを表示するには
currentConditions
と書くことができます。
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
に関する注意事項
jsonObject(with data
:
多くの (すべての) チュートリアルでは
.mutableContainers
または
.mutableLeaves
というオプションは、Swiftでは完全にナンセンスです。この2つのオプションは、レガシーなObjective-Cのオプションであり、結果を
NSMutable...
オブジェクトに割り当てます。Swiftでは、どんな
var
イアブルはデフォルトでミュータブルであり、これらのオプションのいずれかを渡してその結果を
let
定数に代入しても全く効果がありません。さらに、ほとんどの実装では、とにかくデシリアライズされたJSONを決して変異させません。
Swiftで有用な唯一の(稀な)オプションは
.allowFragments
で、これは JSON ルートオブジェクトが値型 (
String
,
Number
,
Bool
または
null
) ではなく、コレクションタイプ (
array
または
dictionary
). しかし、通常は
options
というパラメータは
オプションなし
.
===========================================================================
JSONをパースするための一般的な注意点
JSONはよく整理されたテキスト形式です。JSONの文字列を読むのはとても簡単です。 文字列をよく読む . 2つのコレクション型と4つの値型の6種類だけです。
コレクション型は
-
配列
- JSON: 角括弧内のオブジェクト
[]
- Swift[Any]
しかし、ほとんどの場合[[String:Any]]
-
辞書
- JSON: 中括弧で囲まれたオブジェクト
{}
- Swift[String:Any]
値のタイプは
-
文字列
- JSON: 二重引用符で囲まれた任意の値
"Foo"
であっても"123"
または"false"
- スウィフトString
-
番号
- JSON: 数値
ではない
二重引用符で囲む
123
または123.0
- スウィフトInt
またはDouble
-
ブール
- JSONです。
true
またはfalse
ではない を二重引用符で囲む - Swift:true
またはfalse
-
ヌル
- JSONです。
null
- SwiftNSNull
JSONの仕様では、辞書のキーはすべて
String
.
基本的に、オプショナルバインディングを使用してオプショナルを安全にアンラップすることが推奨されます。
ルートオブジェクトが辞書の場合 (
{}
) に型をキャストします。
[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
でキーを指定して値を取得し、(
OneOfSupportedJSONTypes
は上記のようにJSONコレクションか値の型です)。
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
ルートオブジェクトが配列である場合 (
[]
) にキャストしてください。
[[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
という配列で反復処理します。
for item in parsedData {
print(item)
}
特定のインデックスの項目が必要な場合、そのインデックスが存在するかどうかもチェックします。
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
JSONがコレクション型ではなく、単に値の型の1つであるという稀なケースは
.allowFragments
オプションを渡し、その結果を適切な値型にキャストする必要があります。
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
AppleはSwift Blogで包括的な記事を公開しています。 SwiftでJSONを扱う
===========================================================================
Swift 4+では
Codable
プロトコルは、JSON を構造体/クラスに直接パースする、より便利な方法を提供します。
例えば、質問で与えられたJSONのサンプル(少し修正されています)
let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""
は、構造体
Weather
. Swift の型は上で説明したものと同じです。いくつかの追加オプションがあります。
-
を表す文字列
URL
は、直接URL
. -
は
time
整数は次のようにデコードされます。Date
でありdateDecodingStrategy
.secondsSince1970
. -
蛇の目状
JSONのキーは
キャメルケース
に変換することができます。
keyDecodingStrategy
.convertFromSnakeCase
struct Weather: Decodable {
let icon, summary: String
let pressure: Double, humidity, windSpeed : Double
let ozone, temperature, dewPoint, cloudCover: Double
let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
let time: Date
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Weather.self, from: data)
print(result)
} catch {
print(error)
}
その他のCodableソース
関連
-
[解決済み] 正しいJSONコンテンツタイプは何ですか?
-
[解決済み] JSONでコメントを使用することはできますか?
-
[解決済み] なぜGoogleはJSONレスポンスにwhile(1);を前置するのでしょうか?
-
[解決済み] cURLでJSONデータをPOSTするにはどうすればよいですか?
-
[解決済み] JavaScriptでJSONをきれいに印刷する
-
[解決済み] JSON文字列を安全にオブジェクトに変換する
-
[解決済み] Chromeを使用してASP.NET Web APIがXMLの代わりにJSONを返すようにするにはどうすればよいですか?
-
[解決済み] UnixツールでJSONをパースする
-
[解決済み] JSONオブジェクトに末尾のカンマを使用することは可能ですか?
-
[解決済み】なぜPythonはこのJSONデータをパースできないのですか?[終了] PythonがこのJSONデータをパースできないのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Twitter API エラー 215
-
[解決済み] BeautifulSoupでJSONオブジェクトから特定の値をパースする
-
TypeError: タイプ 'bytes' のオブジェクトは JSON シリアライズ可能ではありません。
-
[解決済み] ASP.NETでJSONを単純なDictionary<string,string>にデシリアライズするにはどうすればよいですか?
-
[解決済み] JSONの二重引用符をエスケープする方法
-
[解決済み] .NET NewtonSoft JSONのデシリアライズマップを異なるプロパティ名に変更する。
-
[解決済み] PostgresのJSON配列に文字列が含まれているかどうかをチェックする
-
[解決済み] VSCodeでlaunch.jsonに環境変数を追加する方法
-
[解決済み] Mongo コレクションを JSON 形式にダンプする
-
[解決済み] Goマップをjsonに変換する