Ich versuche, eine JSON-Antwort abzurufen und die Ergebnisse in einer Variablen zu speichern. Ich hatte Versionen dieses Codes in früheren Versionen von Swift funktionieren lassen, bis die GM-Version von Xcode 8 veröffentlicht wurde. Ich habe mir einige ähnliche Beiträge zu StackOverflow angesehen: Swift 2 Parsing JSON - Ein Wert vom Typ 'AnyObject' und JSON Parsing in Swift 3 kann nicht abonniert werden .
Es scheint jedoch, dass die dort vermittelten Ideen in diesem Szenario nicht zutreffen.
Wie analysiere ich die JSON-Antwort in Swift 3 korrekt? Hat sich etwas an der Art und Weise geändert, wie JSON in Swift 3 gelesen wird?
Unten ist der fragliche Code (er kann auf einem Spielplatz ausgeführt werden):
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)")
}
}
}
Bearbeiten: Hier ist ein Beispiel der Ergebnisse des API-Aufrufs nachprint(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]
Antworten:
Zu allererst nie Daten geladen werden synchron von einer Remote - URL , verwenden Sie immer asynchrone Methoden wie
URLSession
.tritt auf, weil der Compiler keine Ahnung hat, um welchen Typ es sich bei den Zwischenobjekten handelt (z. B.
currently
in["currently"]!["temperature"]
), und weil Sie Foundation-Auflistungstypen verwenden, wieNSDictionary
der Compiler überhaupt keine Ahnung vom Typ hat.Zusätzlich ist es in Swift 3 erforderlich, den Compiler über den Typ aller tiefgestellten Objekte zu informieren .
Sie müssen das Ergebnis der JSON-Serialisierung in den tatsächlichen Typ umwandeln.
Dieser Code verwendet
URLSession
und ausschließlich Swift native TypenUm alle Schlüssel / Wert-Paare von
currentConditions
Ihnen zu drucken , könnten Sie schreibenEin Hinweis zu
jsonObject(with data
:Viele (es scheint , alle) Anleitungen vorschlagen
.mutableContainers
oder.mutableLeaves
Optionen , die in Swift vollständig ist Unsinn. Die beiden Optionen sind ältere Objective-C-Optionen zum Zuweisen des Ergebnisses zuNSMutable...
Objekten. In Swift ist jedevar
iable standardmäßig veränderbar, und das Übergeben einer dieser Optionen und das Zuweisen des Ergebnisses zu einerlet
Konstanten hat überhaupt keine Auswirkung. Außerdem mutieren die meisten Implementierungen den deserialisierten JSON sowieso nie.Die einzige (selten) Option , die in Swift nützlich ist , ist
.allowFragments
die , wenn erforderlich, wenn das JSON Wurzelobjekt ein Werttyp sein könnte (String
,Number
,Bool
odernull
) , anstatt eine der Sammeltypen (array
oderdictionary
). Aber normalerweise lassen Sie denoptions
Parameter weg , was bedeutet, dass keine Optionen verfügbar sind .================================================== =========================
Einige allgemeine Überlegungen zum Parsen von JSON
JSON ist ein übersichtliches Textformat. Es ist sehr einfach, eine JSON-Zeichenfolge zu lesen. Lesen Sie die Zeichenfolge sorgfältig durch . Es gibt nur sechs verschiedene Typen - zwei Sammlungstypen und vier Werttypen.
Die Sammlungstypen sind
[]
- Swift:[Any]
aber in den meisten Fällen[[String:Any]]
{}
- Swift:[String:Any]
Die Werttypen sind
"Foo"
, auch"123"
oder"false"
- Swift:String
123
oder123.0
- Swift:Int
oderDouble
true
oderfalse
nicht in doppelten Anführungszeichen - Swift:true
oderfalse
null
- Swift:NSNull
Gemäß der JSON-Spezifikation müssen alle Schlüssel in Wörterbüchern sein
String
.Grundsätzlich wird immer empfohlen, optionale Bindungen zu verwenden, um Optionen sicher auszupacken
Wenn das Stammobjekt ein dictionary (
{}
) ist, wandeln Sie den Typ in um[String:Any]
und Werte durch Schlüssel mit abrufen (
OneOfSupportedJSONTypes
ist entweder JSON-Sammlung oder Werttyp wie oben beschrieben.)Wenn das Stammobjekt ein Array (
[]
) ist, wandeln Sie den Typ in um[[String:Any]]
und iteriere durch das Array mit
Wenn Sie ein Element an einem bestimmten Index benötigen, überprüfen Sie auch, ob der Index vorhanden ist
In dem seltenen Fall, dass JSON einfach einer der Werttypen und nicht ein Auflistungstyp ist, müssen Sie die
.allowFragments
Option übergeben und das Ergebnis beispielsweise in den entsprechenden Werttyp umwandelnApple hat einen umfassenden Artikel im Swift-Blog veröffentlicht: Arbeiten mit JSON in Swift
================================================== =========================
In Swift 4+
Codable
bietet das Protokoll eine bequemere Möglichkeit, JSON direkt in Strukturen / Klassen zu analysieren.Zum Beispiel das angegebene JSON-Beispiel in der Frage (leicht modifiziert)
kann in die Struktur dekodiert werden
Weather
. Die Swift-Typen sind die gleichen wie oben beschrieben. Es gibt einige zusätzliche Optionen:URL
können direkt als dekodiert werdenURL
.time
Ganzzahl kann wieDate
bei der dekodiert werdendateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Andere codierbare Quellen:
quelle
dict!["currently"]!
in ein Wörterbuch umwandeln, damit der Compiler sicher auf das nachfolgende Schlüsselabonnement schließen kann.Eine große Änderung, die mit Xcode 8 Beta 6 für Swift 3 passiert ist, war, dass die ID jetzt als importiert wird
Any
und nicht mehrAnyObject
.Dies bedeutet, dass
parsedData
es als Wörterbuch zurückgegeben wird, das höchstwahrscheinlich dem Typ entspricht[Any:Any]
. Ohne einen Debugger könnte ich Ihnen nicht genau sagen, was Ihre BesetzungNSDictionary
tun soll, aber der Fehler, den Sie sehen, ist, weil erdict!["currently"]!
Typ hatAny
Wie lösen Sie das? Ich gehe davon aus, dass
dict!["currently"]!
es sich um ein Wörterbuch handelt, und Sie haben viele Möglichkeiten:Zuerst könnten Sie so etwas tun:
Dadurch erhalten Sie ein Wörterbuchobjekt, das Sie dann nach Werten abfragen und so Ihre Temperatur wie folgt ermitteln können:
Oder wenn Sie es vorziehen, können Sie es in der Linie tun:
Hoffentlich hilft das, ich fürchte, ich hatte keine Zeit, eine Beispiel-App zu schreiben, um sie zu testen.
Ein letzter Hinweis:
[String: AnyObject]
Am einfachsten ist es, die JSON-Nutzdaten gleich zu Beginn einfach zu übertragen .quelle
dict!["currently"]! as! [String: String]
wird abstürzen[String: String]
kann überhaupt nicht funktionieren, da es einige numerische Werte gibt. Es funktioniert nicht in einem Mac Playgroundquelle
Ich habe quicktype genau für diesen Zweck erstellt. Fügen Sie einfach Ihren Beispiel-JSON ein und quicktype generiert diese Typhierarchie für Ihre API-Daten:
Außerdem wird ein abhängigkeitsfreier Marshalling-Code generiert, um den Rückgabewert von
JSONSerialization.jsonObject
in a zu lockenForecast
, einschließlich eines praktischen Konstruktors, der eine JSON-Zeichenfolge verwendet, damit Sie schnell einen stark typisiertenForecast
Wert analysieren und auf seine Felder zugreifen können:Sie können quicktype von npm mit installieren
npm i -g quicktype
oder die Web-Benutzeroberfläche verwenden , um den vollständig generierten Code zum Einfügen in Ihren Spielplatz zu erhalten.quelle
Aktualisiert das
isConnectToNetwork-Function
danach dank dieses Beitrags .Ich habe eine zusätzliche Methode dafür geschrieben:
Jetzt können Sie dies ganz einfach in Ihrer App aufrufen, wo immer Sie möchten
quelle
Swift hat eine mächtige Typinferenz. Lassen Sie uns die Kesselplatte "if let" oder "guard let" loswerden und das Auspacken mit einem funktionalen Ansatz erzwingen:
Nur eine Codezeile und kein gewaltsames Auspacken oder manuelles Gießen. Dieser Code funktioniert auf dem Spielplatz, sodass Sie ihn kopieren und überprüfen können. Hier ist eine Implementierung auf GitHub.
quelle
Dies ist ein anderer Weg, um Ihr Problem zu lösen. Schauen Sie sich also bitte die folgende Lösung an. Hoffe es wird dir helfen.
quelle
Das Problem liegt in der API-Interaktionsmethode. Die JSON-Analyse wird nur in der Syntax geändert. Das Hauptproblem ist das Abrufen von Daten. Was Sie verwenden, ist eine synchrone Methode zum Abrufen von Daten. Das funktioniert nicht in jedem Fall. Was Sie verwenden sollten, ist eine asynchrone Methode zum Abrufen von Daten. Auf diese Weise müssen Sie Daten über die API anfordern und warten, bis sie mit Daten antworten. Sie können dies mit URL-Sitzungen und Bibliotheken von Drittanbietern wie erreichen
Alamofire
. Unten finden Sie den Code für die URL-Sitzungsmethode.quelle
Wenn ich ein Modell mit dem vorherigen json erstelle Verwenden Sie diesen Link [Blog]: http://www.jsoncafe.com , um eine codierbare Struktur oder ein beliebiges Format zu generieren
Modell
Analysieren
quelle