Próbuję pobrać odpowiedź JSON i zapisać wyniki w zmiennej. Miałem wersje tego kodu działające w poprzednich wersjach Swift, aż do wydania wersji GM Xcode 8. Rzuciłem okiem na kilka podobnych postów na StackOverflow: Swift 2 Parsing JSON - Nie można indeksować wartości typu `` AnyObject '' i JSON Parsing w Swift 3 .
Jednak wydaje się, że przedstawione tam pomysły nie mają zastosowania w tym scenariuszu.
Jak poprawnie przeanalizować odpowiedź JSON w Swift 3? Czy coś się zmieniło w sposobie odczytywania JSON w Swift 3?
Poniżej znajduje się kod, o którym mowa (można go uruchomić na placu zabaw):
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)")
}
}
}
Edycja: oto przykład wyników wywołania API poprint(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]
Odpowiedzi:
Przede wszystkim nigdy nie ładuj danych synchronicznie ze zdalnego adresu URL , używaj zawsze metod asynchronicznych, takich jak
URLSession
.występuje, ponieważ kompilator nie ma pojęcia, jakiego typu są obiekty pośrednie (na przykład
currently
w["currently"]!["temperature"]
), a ponieważ używasz typów kolekcji Foundation, tak jakNSDictionary
kompilator nie ma żadnego pojęcia o typie.Dodatkowo w Swift 3 wymagane jest poinformowanie kompilatora o typie wszystkich obiektów z indeksem.
Musisz rzutować wynik serializacji JSON na rzeczywisty typ.
Ten kod używa
URLSession
i wyłącznie typów natywnych SwiftAby wydrukować wszystkie pary klucz / wartość,
currentConditions
możesz napisaćUwaga dotycząca
jsonObject(with data
:Wiele (wydaje się, że wszystkie) samouczków sugeruje
.mutableContainers
lub.mutableLeaves
opcji, co w Swift jest całkowicie nonsensowne. Dwie opcje to starsze opcje Objective-C, służące do przypisywania wyniku doNSMutable...
obiektów. W Swift każdyvar
iable jest domyślnie zmienny i przekazanie którejkolwiek z tych opcji i przypisanie wyniku dolet
stałej nie ma żadnego efektu. Co więcej, większość implementacji i tak nigdy nie mutuje zdeserializowanego JSON.Jedyny (rzadki) rozwiązaniem, które jest przydatne w SWIFT
.allowFragments
który jest wymagany w przypadku, gdy głównym celem JSON może być typu wartość (String
,Number
,Bool
inull
), niż jeden z typów Collection (array
lubdictionary
). Ale zwykle pomijajoptions
parametr, co oznacza brak opcji .==================================================== =========================
Kilka ogólnych uwag dotyczących analizowania formatu JSON
JSON to dobrze zorganizowany format tekstowy. Bardzo łatwo jest odczytać ciąg JSON. Przeczytaj uważnie tekst . Istnieje tylko sześć różnych typów - dwa typy kolekcji i cztery typy wartości.
Typy kolekcji to
[]
- Swift:[Any]
ale w większości przypadków[[String:Any]]
{}
- Swift:[String:Any]
Typy wartości to
"Foo"
, parzysta"123"
lub"false"
- Swift:String
123
lub123.0
- Swift:Int
lubDouble
true
lubfalse
nie w cudzysłowach - Swift:true
lubfalse
null
- Swift:NSNull
Zgodnie ze specyfikacją JSON wszystkie klucze w słownikach muszą być
String
.Zasadniczo zawsze zaleca się używanie opcjonalnych powiązań do bezpiecznego rozpakowywania opcji
Jeśli obiekt główny jest Dictionary (
{}
) rzutem na typ[String:Any]
i pobieraj wartości za pomocą kluczy z (
OneOfSupportedJSONTypes
jest to kolekcja JSON lub typ wartości, jak opisano powyżej).Jeśli obiekt główny jest tablicą (
[]
), rzut na typ[[String:Any]]
i iteruj po tablicy za pomocą
Jeśli potrzebujesz elementu o określonym indeksie, sprawdź również, czy indeks istnieje
W rzadkich przypadkach, gdy JSON jest po prostu jednym z typów wartości - a nie typem kolekcji - musisz przekazać
.allowFragments
opcję i na przykład rzutować wynik na odpowiedni typ wartościFirma Apple opublikowała obszerny artykuł na swoim blogu Swift: Praca z JSON w Swift
==================================================== =========================
W Swift 4+
Codable
protokół zapewnia wygodniejszy sposób analizowania JSON bezpośrednio na struktury / klasy.Na przykład podana próbka JSON w pytaniu (nieznacznie zmodyfikowana)
można zdekodować do struktury
Weather
. Typy jerzyków są takie same, jak opisano powyżej. Istnieje kilka dodatkowych opcji:URL
można dekodować bezpośrednio jakoURL
.time
Całkowita może być dekodowanyDate
zdateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Inne kodowane źródła:
źródło
dict!["currently"]!
na słownik, z którego kompilator może bezpiecznie wywnioskować kolejną subskrypcję klucza.Dużą zmianą, jaka nastąpiła w Xcode 8 Beta 6 dla Swift 3, było to, że id teraz importuje jako
Any
zamiastAnyObject
.Oznacza to, że
parsedData
jest zwracany jako słownik najprawdopodobniej z typem[Any:Any]
. Bez debugera nie mogłem dokładnie powiedzieć, coNSDictionary
zrobi twoje rzutowanie , ale błąd, który widzisz, jest taki, żedict!["currently"]!
ma typAny
Jak więc to rozwiązać? Ze sposobu, w jaki się do niego odwołałeś, zakładam, że
dict!["currently"]!
jest to słownik, więc masz wiele opcji:Najpierw możesz zrobić coś takiego:
W ten sposób otrzymasz obiekt słownikowy, do którego możesz następnie zapytać o wartości, dzięki czemu uzyskasz swoją temperaturę w następujący sposób:
Lub jeśli wolisz, możesz to zrobić w kolejce:
Mam nadzieję, że to pomoże, obawiam się, że nie miałem czasu napisać przykładowej aplikacji, aby ją przetestować.
Ostatnia uwaga: najłatwiejszą rzeczą do zrobienia może być po prostu przerzucenie ładunku JSON
[String: AnyObject]
na samym początku.źródło
dict!["currently"]! as! [String: String]
się rozbije[String: String]
nie może w ogóle działać, ponieważ istnieje kilka wartości liczbowych. To nie działa na placu zabaw dla komputerów Macźródło
Właśnie w tym celu zbudowałem quicktype . Po prostu wklej przykładowy kod JSON, a quicktype wygeneruje następującą hierarchię typów dla danych API:
Generuje również kod organizacyjny bez zależności, aby przekonać wartość zwracaną
JSONSerialization.jsonObject
do aForecast
, w tym wygodny konstruktor, który przyjmuje ciąg JSON, dzięki czemu można szybko przeanalizować silnie wpisanąForecast
wartość i uzyskać dostęp do jej pól:Możesz zainstalować quicktype z npm za pomocą interfejsu internetowego
npm i -g quicktype
lub użyć interfejsu internetowego, aby uzyskać kompletny wygenerowany kod do wklejenia na swoim placu zabaw.źródło
Zaktualizowany się
isConnectToNetwork-Function
potem, dzięki tym poście .Napisałem na to dodatkową metodę:
Teraz możesz łatwo zadzwonić do tego w swojej aplikacji, gdziekolwiek chcesz
źródło
Swift ma potężne wnioskowanie o typie. Pozbądźmy się schematu „if let” lub „guard let” i wymuś rozpinanie używając podejścia funkcjonalnego:
Tylko jedna linia kodu i bez wymuszania rozpakowywania lub ręcznego rzutowania typu. Ten kod działa na placu zabaw, więc możesz go skopiować i sprawdzić. Oto implementacja w GitHub.
źródło
To jest inny sposób rozwiązania problemu. Sprawdź więc poniższe rozwiązanie. Mam nadzieję, że to ci pomoże.
źródło
Problem dotyczy metody interakcji API. Analizowanie JSON zmienia się tylko w składni. Główny problem tkwi w sposobie pobierania danych. To, czego używasz, to synchroniczny sposób pobierania danych. To nie działa w każdym przypadku. Powinieneś używać asynchronicznego sposobu pobierania danych. W ten sposób musisz zażądać danych przez API i poczekać, aż dane odpowiedzą. Możesz to osiągnąć za pomocą sesji URL i bibliotek zewnętrznych, takich jak
Alamofire
. Poniżej znajduje się kod metody sesji URL.źródło
Jeśli utworzę model przy użyciu poprzedniego json Używając tego linku [blog]: http://www.jsoncafe.com do generowania struktury kodowalnej lub dowolnego formatu
Model
Analizować
źródło