1. ホーム
  2. json

[解決済み] Swift 3でJSONを正しくパースする

2022-08-31 13:26:10

質問

私は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 - Swift NSNull

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ソース