私はJSONレスポンスを取得して結果を変数に格納しようとしています。 GMバージョンのXcode 8がリリースされるまで、私は以前のリリースのSwiftでこのコードのバージョンを動作させました。私はStackOverflowに関する同様の投稿をいくつか見てみました。 Swift 2 Parsing JSON - 'AnyObject'型の値に添え字を付けることはできません そして Swift 3のJSON Parsing 。
ただし、そこで伝えられたアイデアはこのシナリオには当てはまりません。
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)")
}
}
}
編集: これはprint(currentConditions)
の後のAPI呼び出しの結果のサンプルです。
["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"]!["temperature"]
のcurrently
)、NSDictionary
などのFoundationコレクションタイプを使用しているため、コンパイラがそのタイプをまったく認識していないためです。
さらに、Swift 3では、添え字付きオブジェクトallのタイプについてコンパイラーに通知する必要があります。
JSONシリアル化の結果を実際の型にキャストする必要があります。
このコードはURLSession
およびexclusivelySwiftネイティブ型を使用します
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
に関する注意::
多くの(すべてのように思われる)チュートリアルでは、Swiftではまったく意味のない.mutableContainers
または.mutableLeaves
オプションを推奨しています。 2つのオプションは、結果をNSMutable...
オブジェクトに割り当てるための従来のObjective-Cオプションです。 Swiftでは、var
iableはデフォルトで変更可能であり、これらのオプションのいずれかを渡し、結果をlet
定数に割り当ててもまったく効果がありません。さらに、ほとんどの実装は、とにかくデシリアライズされたJSONを変更しません。
Swiftで役立つ唯一の(まれな)オプションは.allowFragments
であり、JSONルートオブジェクトが値タイプ(String
、Number
、Bool
、またはnull
)の場合は必須ですコレクションの種類(array
またはdictionary
)。ただし、通常はオプションなしを意味するoptions
パラメーターを省略します。
================================================== =========================
JSONは、適切に配置されたテキスト形式です。 JSON文字列を読み取るのは非常に簡単です。 文字列を注意深く読んでください。 2つのコレクションタイプと4つの値タイプの6つの異なるタイプのみがあります。
コレクションのタイプは
[]
-Swift:[Any]
ですが、ほとんどの場合[[String:Any]]
{}
-Swift:[String:Any]
値のタイプは
"Foo"
、さらには"123"
または"false"
– Swift:String
123
または123.0
– Swift:Int
またはDouble
true
またはfalse
not二重引用符で囲む– Swift:true
またはfalse
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ブログで包括的な記事を公開しています: Working with JSON in Swift
================================================== =========================
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}
"""
struct Weather
にデコードできます。 Swiftタイプは上記と同じです。追加のオプションがいくつかあります。
URL
を表す文字列は、URL
として直接デコードできます。time
整数は、Date
.secondsSince1970
を使用してdateDecodingStrategy
としてデコードできます。keyDecodingStrategy
.convertFromSnakeCase
でcamelCaseに変換できます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)
}
その他のコード化可能なソース:
Swift 3のXcode 8 Beta 6で起こった大きな変化は、idがAny
ではなくAnyObject
としてインポートされるようになったことです。
これは、parsedData
が[Any:Any]
型の辞書として返されることを意味します。デバッガを使用しないと、NSDictionary
へのキャストが何をするのか正確にはわかりませんが、表示されるエラーはdict!["currently"]!
の型がAny
であるためです
それで、あなたはこれをどのように解決しますか?あなたがそれを参照した方法から、私はdict!["currently"]!
が辞書であると思います、そして、あなたは多くのオプションがあります:
まず、あなたはこのようなことをすることができます:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
これはあなたが値を問い合わせることができる辞書オブジェクトをあなたに与えるでしょう、そしてあなたはこのようにあなたの体温を得ることができます:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
あるいは、あなたが望むなら、あなたはそれをインラインで行うことができます:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
うまくいけば、これが役立ちます、私はそれをテストするためのサンプルアプリを書く時間がなかったと思います。
最後の注意事項:最も簡単なことは、JSONペイロードを最初の[String: AnyObject]
にキャストすることです。
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String]
{
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
この目的のために quicktype を構築しました。サンプルのJSONを貼り付けるだけで、quicktypeがAPIデータのこの型階層を生成します。
struct Forecast {
let hourly: Hourly
let daily: Daily
let currently: Currently
let flags: Flags
let longitude: Double
let latitude: Double
let offset: Int
let timezone: String
}
struct Hourly {
let icon: String
let data: [Currently]
let summary: String
}
struct Daily {
let icon: String
let data: [Datum]
let summary: String
}
struct Datum {
let precipIntensityMax: Double
let apparentTemperatureMinTime: Int
let apparentTemperatureLowTime: Int
let apparentTemperatureHighTime: Int
let apparentTemperatureHigh: Double
let apparentTemperatureLow: Double
let apparentTemperatureMaxTime: Int
let apparentTemperatureMax: Double
let apparentTemperatureMin: Double
let icon: String
let dewPoint: Double
let cloudCover: Double
let humidity: Double
let ozone: Double
let moonPhase: Double
let precipIntensity: Double
let temperatureHigh: Double
let pressure: Double
let precipProbability: Double
let precipIntensityMaxTime: Int
let precipType: String?
let sunriseTime: Int
let summary: String
let sunsetTime: Int
let temperatureMax: Double
let time: Int
let temperatureLow: Double
let temperatureHighTime: Int
let temperatureLowTime: Int
let temperatureMin: Double
let temperatureMaxTime: Int
let temperatureMinTime: Int
let uvIndexTime: Int
let windGust: Double
let uvIndex: Int
let windBearing: Int
let windGustTime: Int
let windSpeed: Double
}
struct Currently {
let precipProbability: Double
let humidity: Double
let cloudCover: Double
let apparentTemperature: Double
let dewPoint: Double
let ozone: Double
let icon: String
let precipIntensity: Double
let temperature: Double
let pressure: Double
let precipType: String?
let summary: String
let uvIndex: Int
let windGust: Double
let time: Int
let windBearing: Int
let windSpeed: Double
}
struct Flags {
let sources: [String]
let isdStations: [String]
let units: String
}
また、JSONSerialization.jsonObject
の戻り値をForecast
に変換するための依存性のないマーシャリングコードも生成します。これには、厳密に型指定されたForecast
値をすばやく解析してそのフィールドにアクセスできる便利なコンストラクタが含まれます。
let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
あなたはnpmからnpm i -g quicktype
を使ってquicktypeをインストールすることができますまたは あなたの遊び場に貼り付けるために完全な生成コードを得るために Web UIを使用してください - /。
更新あとで、この投稿のおかげでisConnectToNetwork-Function Swiftとのインターネット接続を確認します
私はそれのために追加のメソッドを書きました:
import SystemConfiguration
func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {
if(isConnectedToNetwork() == false){
completionHandler("-1" as AnyObject)
return
}
let request = NSMutableURLRequest(url: URL(string: link)!)
request.httpMethod = "POST"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
//JSON successfull
do {
let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
DispatchQueue.main.async(execute: {
completionHandler(parseJSON as AnyObject)
});
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
task.resume()
}
func isConnectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
let ret = (isReachable && !needsConnection)
return ret
}
だから今、あなたは簡単にあなたが欲しいところはどこでもあなたのアプリでこれを呼び出すことができます
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") {
parseJSON in
if(String(describing: parseJSON) == "-1"){
print("No Internet")
} else {
if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
//... do stuff
}
}
問題はAPIインタラクションメソッドにあります。JSONの構文解析はSyntaxでのみ変更されます。主な問題はデータの取得方法にあります。あなたが使用しているのはデータを取得する同期的な方法です。どのような場合でもこれはうまくいきません。あなたが使うべきものはデータをフェッチするための非同期的な方法です。このように、あなたはAPIを通してデータを要求し、それがデータで応答するのを待つ必要があります。あなたはURLセッションとAlamofireのようなサードパーティのライブラリでこれを達成することができます。以下はURLセッションメソッドのコードです。
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
guard error == nil else {
print(error)
}
do {
let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]()
//Now your data is parsed in Data variable and you can use it normally
let currentConditions = Data["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}.resume()
Swiftは強力な型推論をします。 「if let」または「guard let」の定型句を削除し、機能的なアプローチを使用してラップを解除することができます。
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in
{ f(a, $0) }
}
}
/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
return json.flatMap {
cast($0[key])
}
}
/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
return curry(extract)(key)
}
/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
associativity: right
higherThan: AssignmentPrecedence
lowerThan: TernaryPrecedence
}
/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
return f(a)
}
/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
return a as? B
}
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound
1行のコードで、強制的に展開したり手動で型キャストしたりすることはありません。このコードは遊び場で機能するので、コピーして確認することができます。これがGitHubでの実装 です。