it-swarm.com.de

JSON in Swift richtig analysieren 3

Ich versuche, eine JSON-Antwort abzurufen und die Ergebnisse in einer Variablen zu speichern. Ich hatte in früheren Versionen von Swift Versionen dieses Codes, bis die GM - Version von Xcode 8 veröffentlicht wurde. Ich habe mir ein paar ähnliche Posts über StackOverflow angesehen: Swift 2 Parsing JSON - Kann keinen Wert des Typs 'AnyObject' und JSON Parsing in Swift 3 subskribieren. 

Es scheint jedoch, dass die dort übermittelten Ideen in diesem Szenario nicht zutreffen.

Wie analysiere ich die JSON-Antwort in Swift 3 richtig? Hat sich etwas an der Art geändert, wie JSON in Swift 3 gelesen wird?

Hier ist der betreffende Code (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)")
        }
    }
}

Edit: Hier ist ein Beispiel der Ergebnisse des API-Aufrufs nach 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]
109
user2563039

Laden Sie niemals Daten synchron von einer entfernten URL, verwenden Sie immer asynchrone Methoden wie URLSession.

'Any' hat keine tiefgestellten Mitglieder

tritt auf, weil der Compiler keine Ahnung hat, um welchen Typ es sich bei den Zwischenobjekten handelt (z. B. currently in ["currently"]!["temperature"]) und da Sie Foundation-Auflistungstypen wie NSDictionary verwenden, hat der Compiler keine Ahnung von dem Typ.

Zusätzlich ist es in Swift 3 erforderlich, den Compiler über den Typ von all subskribierten Objekten zu informieren.

Sie müssen das Ergebnis der JSON-Serialisierung in den tatsächlichen Typ umwandeln.

Dieser Code verwendet die systemeigenen URLSession- und ausschließlich Swift-Typen

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()

Um alle Schlüssel/Wert-Paare von currentConditions zu drucken, können Sie schreiben

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

Ein Hinweis zu jsonObject(with data:

Viele (es scheint alle) Tutorials schlagen .mutableContainers oder .mutableLeaves Optionen vor, was in Swift völlig unsinnig ist. Die beiden Optionen sind ältere Objective-C-Optionen, um das Ergebnis NSMutable...-Objekten zuzuweisen. In Swift ist jedes variable standardmäßig veränderbar. Das Übergeben einer dieser Optionen und das Zuweisen des Ergebnisses zu einer let-Konstante hat keinerlei Auswirkungen. Außerdem mutieren die meisten Implementierungen ohnehin niemals die deserialisierte JSON.

Die einzige (seltene) Option, die in Swift nützlich ist, ist .allowFragments. Dies ist erforderlich, wenn das JSON-Stammobjekt ein Werttyp (String, Number, Bool oder null) anstelle eines der Auflistungstypen (array oder dictionary) sein kann. Lassen Sie aber normalerweise den Parameter options weg, der Keine Optionen bedeutet.

================================================== ===========================

Einige allgemeine Überlegungen zum Analysieren von JSON

JSON ist ein übersichtliches Textformat. Es ist sehr einfach, eine JSON-Zeichenfolge zu lesen. Lesen Sie die Zeichenfolge sorgfältig. Es gibt nur sechs verschiedene Typen - zwei Erfassungstypen und vier Werttypen.


Die Kollektionstypen sind

  • Array - JSON: Objekte in eckigen Klammern [] - Swift: [Any] aber in den meisten Fällen [[String:Any]]
  • Dictionary - JSON: Objekte in geschweiften Klammern {} - Swift: [String:Any]

Die Werttypen sind

  • String - JSON: beliebiger Wert in Anführungszeichen "Foo", auch "123"oder "false" - Swift: String
  • Number - JSON: numerische Werte nicht in Anführungszeichen 123 oder 123.0 - Swift: Int oder Double
  • Bool - JSON: true oder falsenot in doppelten Anführungszeichen - Swift: true oder false
  • null - JSON: null - Swift: NSNull

Gemäß der JSON-Spezifikation müssen alle Schlüssel in Wörterbüchern String sein. 


Grundsätzlich wird empfohlen, optionale Bindungen zum sicheren Entpacken von Optionals zu verwenden.

Wenn das Stammobjekt ein Wörterbuch ({}) ist, wandeln Sie den Typ in [String:Any] um.

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...

und rufen Sie Werte mit Hilfe von Schlüsseln mit ab (OneOfSupportedJSONTypes ist entweder eine JSON-Sammlung oder ein Werttyp, wie oben beschrieben.)

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 

Wenn das Stammobjekt ein Array ([]) ist, wandeln Sie den Typ in [[String:Any]] um.

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...

und durchlaufen das Array mit 

for item in parsedData {
    print(item)
}

Wenn Sie einen Artikel für einen bestimmten Index benötigen, prüfen Sie auch, ob der Index vorhanden ist

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
   let item = parsedData[2] as? OneOfSupportedJSONTypes {
      print(item)
    }
}

In den seltenen Fällen, in denen JSON nur einer der Werttypen ist - und nicht ein Auflistungstyp -, müssen Sie die Option .allowFragments übergeben und das Ergebnis beispielsweise in den entsprechenden Wertetyp umwandeln 

if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...

Apple hat einen umfassenden Artikel im Swift-Blog veröffentlicht: Arbeiten mit JSON in Swift

Update: In Swift 4+ bietet das Codable-Protokoll eine bequemere Möglichkeit, JSON direkt in Strukturen/Klassen zu parsen.

157
vadian

Eine große Änderung, die mit Xcode 8 Beta 6 für Swift 3 stattfand, war, dass die ID jetzt als Any und nicht als AnyObject importiert wird.

Dies bedeutet, dass parsedData als Wörterbuch der wahrscheinlichsten mit dem Typ [Any:Any] zurückgegeben wird. Ohne einen Debugger konnte ich Ihnen nicht genau sagen, was Ihre Besetzung in NSDictionary tun wird, aber der Fehler, den Sie sehen, ist, dass dict!["currently"]! den Typ Any hat

Wie lösen Sie das? Ich gehe davon aus, wie Sie darauf verwiesen haben, dass dict!["currently"]! ein Wörterbuch ist, und Sie haben viele Optionen:

Zuerst könnte man so etwas machen: 

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

Dadurch erhalten Sie ein Wörterbuchobjekt, das Sie anschließend nach Werten abfragen können. So können Sie Ihre Temperatur wie folgt ermitteln: 

let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double

Oder wenn Sie es vorziehen, können Sie es in einer Reihe machen: 

let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double

Hoffentlich hilft das, ich fürchte, ich hatte keine Zeit, eine Beispiel-App zu schreiben, um sie zu testen.

Eine letzte Anmerkung: Am einfachsten ist es, wenn Sie die JSON-Nutzlast gleich zu Beginn in [String: AnyObject] ablegen.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
12
discorevilo
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)")
}
6
BhuShan PaWar

Ich habe quicktype genau für diesen Zweck gebaut. Fügen Sie einfach Ihre Beispiel-JSON ein und quicktype generiert diese Typhierarchie für Ihre API-Daten:

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
}

Es generiert auch einen Abhängigkeit-Marshalling-Code, um den Rückgabewert von JSONSerialization.jsonObject in eine Forecast zu überführen, einschließlich eines Convenience-Konstruktors, der einen JSON-String verwendet, damit Sie einen stark typisierten Forecast-Wert schnell analysieren und auf seine Felder zugreifen können:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

Sie können Quicktype von npm mit npm i -g quicktype installieren oder verwenden Sie die Web-Benutzeroberfläche , um den vollständig generierten Code in Ihren Spielplatz einzufügen.

4
David Siegel

Aktualisiert anschließend die isConnectToNetwork-Funktion, dank diesem Beitrag Mit Swift auf Internetverbindung prüfen

ich habe eine extra Methode dafür geschrieben: 

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

}

So können Sie dies jetzt ganz einfach in Ihrer App aufrufen, wo immer Sie möchten

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
                }
  }
3
Marco Weber

Das Problem liegt bei der API-Interaktionsmethode. Die JSON-Analyse wird nur in Syntax geändert. Das Hauptproblem ist die Art und Weise, wie Daten abgerufen werden. Was Sie verwenden, ist ein synchroner Weg, um Daten abzurufen. 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 darauf warten, dass sie mit Daten antworten. Dies erreichen Sie mit URL-Sitzungen und Bibliotheken von Drittanbietern wie Alamofire. Nachfolgend finden Sie die Sitzungsmethode für Code für 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()
0
Arun K

Swift hat einen mächtigen Typschluss. Lassen Sie uns "Wenn sich" oder "Wache lassen" entfernen, und zwingen Sie das Abwickeln mit einem funktionalen Ansatz:

  1. Hier ist unser JSON. Wir können optionales JSON oder normal verwenden. Ich verwende optional in unserem Beispiel:

    let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]

  1. Hilfsfunktionen. Wir müssen sie nur einmal schreiben und dann mit jedem Wörterbuch wiederverwenden:

    /// 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
    }

  1. Und hier ist unsere Magie - extrahieren Sie den Wert:

    let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

Nur eine Codezeile und kein Force-Wrapping oder manuelles Casting. Dieser Code funktioniert auf dem Spielplatz, sodass Sie ihn kopieren und überprüfen können. Hier ist eine Implementierung auf GitHub.

0
J. Doe