2017-10-24 4 views
1

Betrachtet den folgenden Kern: https://gist.github.com/anonymous/0703a591f97fa9f6ad35b29234805fbd - keine effiziente Möglichkeit, alle relevanten Informationen anzuzeigen, da die Dateien von beträchtlicher Länge sind. EntschuldigungDecoding JSON-Objekt (Senden GET-Anfrage mit Parametern) - Swift 4 Decodable

Ich habe einige verschachtelte JSON-Objekte, die ich versuche zu dekodieren.

{ 
    "establishments":[ 
     { ... } 
    ], 
    "meta":{ ... }, 
    "links":[ 

    ] 
} 

^Die establishments Array enthält Establishment Objekte

Mein Code erfolgreich ausgeführt wird, bis die Leitung 7 von ViewController.swift, wo ich tatsächlich nutzen JSONDecode() Funktionalität des Swift 4.

ViewController.swift

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 

URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 
    //print("test") 
    do { 
     let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
     print(establishments) 
    } catch { 
     print("summots wrong") 
    } 

}).resume() 

Der Ausgang I für das zugeordnete JSON-Objekt in Establishment.swift gesehen bekommen ist: summots wrong - auf Linie 11 von ViewController.swift.

Ich glaube, der Grund, es ist nicht wie erwartet funktioniert, weil das JSON-Format wie so ist:

{ 
    "establishments":[ ... ], 
    "meta":{ ... }, 
    "links":[ ] 
} 

aber ich weiß nicht, ob meine Klassenstruktur /, was ich in meinen Ansicht-Controller zu tun erfüllt diese Layoutkriterien.

Kann mir jemand sehen, wo ich falsch liege? Ich denke, es ist etwas sehr einfach, aber ich kann meinen Kopf nicht scheinen, um es zu bekommen

Ich verstehe auch, dass ich nicht zu nennen Attribute von Klassen mit einem Großbuchstaben soll ich - entschuldigen uns für die Konventionen brechen

EDIT: ich druckte den Fehler statt meiner eigenen Aussage und es ist wie folgt:

statt: print("summots wrong")

ich schrieb: print(error)

dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}))) 

Wie Hamish richtig bemerkte, erhalte ich keine korrekten JSON-Daten von der GET-Anfrage. Tatsächlich erhalte ich überhaupt kein JSON. Die verwendete API verwendet standardmäßig das XML-Format, wenn x-api-version nicht auf 2 eingestellt ist.

Statt //print("test") in meiner ViewController.swift Datei, habe ich die empfohlene: print(String(data: data, encoding: .utf8)) Linie

I Postman mit den folgenden Header verwendet: x-api-version: 2, accept: application/json und content-type: application/json meine JSON Ausgabe abrufen. Wenn ich die gleiche GET-Zeichenfolge (mit allen Parametern richtig ausgefüllt) in meinem Browserfenster verwende, erhalte ich eine Fehlermeldung: The API 'Establishments' doesn't exist in einem XML-Format.

Gibt es eine Möglichkeit für mich, einen Header mit dieser URL zu senden?

EDIT 2: Ich habe die Anforderung geändert URLRequest statt nur URL zu verwenden.

Hier ist meine aktualisierte ViewController.swift Code:

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 
var request = URLRequest(url: url) 
request.httpMethod = "GET" 
request.addValue("x-api-version", forHTTPHeaderField: "2") 
request.addValue("accept", forHTTPHeaderField: "application/json") 
request.addValue("content-type", forHTTPHeaderField: "application/json") 

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 
    print(String(data: data, encoding: .utf8)) 
// do { 
//  let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
//  print(establishments) 
// } catch { 
//  print(error) 
// } 

}).resume() 

ich den Fehler bekommen: HTTP Error 400. The request has an invalid header name

ich einige Prüfungen haben es die Anforderung ignoriert einfach den x-api-version Header-Wert von 2.

Irgendwelche Ideen?

EDIT 3: Das richtige Format für addValue hätte sein sollen: request.addValue("2", forHTTPHeaderField: 'x-api-version und so weiter.

Ich bin jetzt konfrontiert mit einem Problem mit meiner Klasse Erklärung, speziell in meiner 'Meta' Klasse.

Meta.(CodingKeys in _DF4B170746CD5543281B14E0B0E7F6FB).dataSource], debugDescription: "Expected String value but found null instead.", underlyingError: nil

Es beschwert, weil meine Klassendeklaration aus dem JSON-Objekt es erwartet wird, die Daten nicht übereinstimmen.

In meiner JSON-Antwort (auf den Kern bezieht) gibt es zwei meta Objekte, enthält man den Wert von null für dataSource und die anderen Lucene zu sein.

Gibt es eine Möglichkeit für mich, Null-Werte sowie Strings in meinen Initialisierern zu akzeptieren, alles unter Einhaltung des Decodable-Protokolls?

Dieser Abschnitt der JSON-Antwort ist für mich tot, muss ich noch Objekte dafür erstellen, oder kann ich einfach damit davonkommen, sie zu ignorieren? - ohne irgendetwas zu erklären. Oder muss ich speziell alles in der JSON-Antwort notieren, um die Daten verwenden zu können?

+0

Kopieren Sie bitte die relevanten Teile Ihres Codes (vorzugsweise ein [mcve]) und fügen Sie sie in den Fragenkörper selbst ein, anstatt eine Verknüpfung zu einem Kernpunkt herzustellen. Es ist nicht zu erwarten, dass wir diese Seite verlassen, um Ihre Frage zu verstehen. – Hamish

+0

Danke für die konstruktive Kritik Jungs, ich habe ein paar Änderungen an der Frage gemacht –

+0

Ich lief die JSON-Ausgabe über einen JSON-Validator und alles scheint gültig zu sein. Irgendwelche Ideen? : \ –

Antwort

1

Um automatisch dekodieren zu können, müssen Ihre Modellklasseneigenschaften mit Antwortwerttypen abgeglichen werden. Von der JSON-Antwort kann ich sehen, dass einige Datenmodelleigenschaften nicht mit dem JSON-Werttyp übereinstimmen. Beispiel:

Entweder müssen Sie Modellklasseneigenschaften als Antwort oder JSON-Antwort als Ihre Klasseneigenschaften ändern.

Zur Demonstration, ich habe Ihre Modellklasse mit folgenden Änderungen geändert:

 // changed from String 
     let Hygiene: Int 
     let Structural: Int 
     let ConfidenceInManagement: Int 

     // changed from Double 
     let longitude: String 
     let latitude: String 

     // changed to optional 
     let dataSource: String? 
     let returncode: String? 

     // changed from Int 
     let RatingValue: String 

Ersetzen Sie Ihre Modellklassen mit folgenden Änderungen:

class Scores: Decodable { 
     // set as string because we can expect values such as 'exempt' 
     let Hygiene: Int 
     let Structural: Int 
     let ConfidenceInManagement: Int 

     init(Hygiene: Int, Structural: Int, ConfidenceInManagement: Int) { 
      self.Hygiene = Hygiene 
      self.Structural = Structural 
      self.ConfidenceInManagement = ConfidenceInManagement 
     } 
    } 

    class Geocode: Decodable { 
     let longitude: String 
     let latitude: String 

     init(longitude: String, latitude: String) { 
      self.longitude = longitude 
      self.latitude = latitude 
     } 
    } 

    class Meta: Decodable { 
     let dataSource: String? 
     let extractDate: String 
     let itemCount: Int 
     let returncode: String? 
     let totalCount: Int 
     let totalPages: Int 
     let pageSize: Int 
     let pageNumber: Int 

     init(dataSource: String, extractDate: String, itemCount: Int, returncode: String, totalCount: Int, totalPages: Int, pageSize: Int, pageNumber: Int) { 

      self.dataSource = dataSource 
      self.extractDate = extractDate 
      self.itemCount = itemCount 
      self.returncode = returncode 
      self.totalCount = totalCount 
      self.totalPages = totalPages 
      self.pageSize = pageSize 
      self.pageNumber = pageNumber 
     } 
    } 

    class Establishments: Decodable { 
     let establishments: [Establishment] 

     init(establishments: [Establishment]) { 
      self.establishments = establishments 
     } 
    } 

    class Establishment: Decodable { 
     let FHRSID: Int 
     let LocalAuthorityBusinessID: String 
     let BusinessName: String 
     let BusinessType: String 
     let BusinessTypeID: Int 
     let AddressLine1: String 
     let AddressLine2: String 
     let AddressLine3: String 
     let AddressLine4: String 
     let PostCode: String 
     let Phone: String 
     let RatingValue: String 
     let RatingKey: String 
     let RatingDate: String 
     let LocalAuthorityCode: Int 
     let LocalAuthorityName: String 
     let LocalAuthorityWebSite: String 
     let LocalAuthorityEmailAddress: String 
     let scores: Scores 
     let SchemeType: String 
     let geocode: Geocode 
     let RightToReply: String 
     let Distance: Double 
     let NewRatingPending: Bool 
     let meta: Meta 
     let links: [String] 

     init(FHRSID: Int, LocalAuthorityBusinessID: String, BusinessName: String, BusinessType: String, BusinessTypeID: Int, AddressLine1: String, AddressLine2: String, AddressLine3: String, AddressLine4: String, PostCode: String, Phone: String, RatingValue: String, RatingKey: String, RatingDate: String, LocalAuthorityCode: Int, LocalAuthorityName: String, LocalAuthorityWebSite: String, LocalAuthorityEmailAddress: String, scores: Scores, SchemeType: String, geocode: Geocode, RightToReply: String, Distance: Double, NewRatingPending: Bool, meta: Meta, links: [String]) { 

      self.FHRSID = FHRSID 
      self.LocalAuthorityBusinessID = LocalAuthorityBusinessID 
      self.BusinessName = BusinessName 
      self.BusinessType = BusinessType 
      self.BusinessTypeID = BusinessTypeID 
      self.AddressLine1 = AddressLine1 
      self.AddressLine2 = AddressLine2 
      self.AddressLine3 = AddressLine3 
      self.AddressLine4 = AddressLine4 
      self.PostCode = PostCode 
      self.Phone = Phone 
      self.RatingValue = RatingValue 
      self.RatingKey = RatingKey 
      self.RatingDate = RatingDate 
      self.LocalAuthorityCode = LocalAuthorityCode 
      self.LocalAuthorityName = LocalAuthorityName 
      self.LocalAuthorityWebSite = LocalAuthorityWebSite 
      self.LocalAuthorityEmailAddress = LocalAuthorityEmailAddress 
      self.scores = scores 
      self.SchemeType = SchemeType 
      self.geocode = geocode 
      self.RightToReply = RightToReply 
      self.Distance = Distance 
      self.NewRatingPending = NewRatingPending 
      self.meta = meta 
      self.links = links 
     } 
    } 

Hoffen, dass es funktionieren wird.

+0

Danke, das hat enorm geholfen. Ich dachte, ich hätte das schon mal probiert (als optionale Parameter, aber offensichtlich nicht alle 'null'-Werte) –

1

Dieser Code sollte Daten als Json abrufen, mit dem richtigen Inhaltstyp in Headern festgelegt.

let jsonURLString = "http://api.ratings.food.gov.uk/Establishments?address=\(self.getPostalCode(place: place))&latitude=\(place.coordinate.latitude.description)&longitude=\(place.coordinate.longitude.description)&maxDistanceLimit=0&name=\(truncatedEstablishmentName[0].replacingOccurrences(of: "'", with: ""))" 

guard let url = URL(string: jsonURLString) else { print("URL is invalid"); return } 
var request = URLRequest(url: url) 
request.httpMethod = "GET" 
request.addValue("2", forHTTPHeaderField: "x-api-version") 
request.addValue("application/json", forHTTPHeaderField: "accept") 
request.addValue("application/json", forHTTPHeaderField: "content-type") 

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in 
    guard let data = data, error == nil, response != nil else { 
     print("Something went wrong") 
     return 
    } 

    do { 
    let establishments = try JSONDecoder().decode(Establishments.self, from: data) 
    print(establishments) 
    } catch { 
    print(error) 
    } 
}).resume() 

Dann ist jede Eigenschaft, die null Bedürfnisse sein kann als optional deklariert werden. nur data-source Betrachtet man null sein kann, Ihre Meta-Objektdeklaration wie folgt sein:

class Meta: Decodable { 
    let dataSource: String? 
    let extractDate: String 
    let itemCount: Int 
    let returncode: String 
    let totalCount: Int 
    let totalPages: Int 
    let pageSize: Int 
    let pageNumber: Int 
} 

Als abschließende Bemerkung, wenn Sie Eigenschaften überall in Ihrem Modell kleingeschrieben haben bevorzugen, können Sie die Schlüssel von overriding the CodingKey enum neu definieren.

Verwandte Themen