Я пытаюсь получить ответ JSON и сохранить результаты в переменной. У меня были версии этого кода, работавшие в предыдущих выпусках Swift, пока не была выпущена GM-версия Xcode 8. Я просмотрел несколько похожих сообщений на StackOverflow: Swift 2 Parsing JSON - Невозможно присвоить индекс значения типа AnyObject и JSON Parsing в Swift 3 .
Однако, похоже, идеи, изложенные там, неприменимы в этом сценарии.
Как правильно разобрать ответ JSON в Swift 3? Что-то изменилось в способе чтения JSON в Swift 3?
Ниже приведен рассматриваемый код (его можно запустить на детской площадке):
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)")
}
}
}
Изменить: вот пример результатов вызова API после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]
Ответы:
Прежде всего, никогда не загружайте данные синхронно с удаленного URL-адреса , всегда используйте асинхронные методы, например
URLSession
.происходит потому, что компилятор не знает, к какому типу относятся промежуточные объекты (например,
currently
в["currently"]!["temperature"]
), и поскольку вы используете типы коллекций Foundation, такиеNSDictionary
как компилятор, не имеет никакого представления о типе.Кроме того, в Swift 3 требуется сообщить компилятору тип всех объектов с индексами.
Вы должны привести результат сериализации JSON к фактическому типу.
Этот код использует
URLSession
и исключительно Swift собственные типыЧтобы распечатать все пары ключ / значение,
currentConditions
вы можете написатьПримечание относительно
jsonObject(with data
:Многие (кажется, все) учебники предлагают
.mutableContainers
или.mutableLeaves
варианты, что в Swift - полная ерунда. Эти две опции являются устаревшими опциями Objective-C для присвоения результатаNSMutable...
объектам. В Swift любойvar
iable является изменяемым по умолчанию, и передача любого из этих параметров и присвоение результатаlet
константе вообще не имеет никакого эффекта. Кроме того, большинство реализаций никогда не изменяют десериализованный JSON.Только (редко) вариант , который является полезным в Swift является
.allowFragments
который необходим , если если корневой объект JSON , может быть типом значения (String
,Number
,Bool
илиnull
) , а не один из типов коллекций (array
илиdictionary
). Но обычноoptions
параметр опускается, что означает « Нет параметров» .================================================== =========================
Некоторые общие соображения по синтаксическому анализу JSON
JSON - это хорошо организованный текстовый формат. Читать строку JSON очень легко. Внимательно прочтите строку . Всего существует шесть различных типов - два типа коллекций и четыре типа значений.
Типы коллекций:
[]
- Swift:[Any]
но в большинстве случаев[[String:Any]]
{}
- Swift:[String:Any]
Типы значений:
"Foo"
, даже"123"
или"false"
- Swift:String
123
или123.0
- Swift:Int
илиDouble
true
илиfalse
без двойных кавычек - Swift:true
илиfalse
null
- Swift:NSNull
Согласно спецификации JSON все ключи в словарях должны быть
String
.В принципе, всегда рекомендуется использовать необязательные привязки для безопасного развертывания необязательных параметров.
Если корневой объект является словарем (
{}
), приведите тип к[String:Any]
и получать значения по ключам с (
OneOfSupportedJSONTypes
это либо коллекция JSON, либо тип значения, как описано выше).Если корневой объект является array (
[]
), приведите тип к[[String:Any]]
и перебрать массив с помощью
Если вам нужен элемент в определенном индексе, проверьте также, существует ли индекс
В том редком случае, когда JSON является просто одним из типов значений, а не типом коллекции, вы должны передать
.allowFragments
параметр и привести результат к соответствующему типу значения, напримерApple опубликовала исчерпывающую статью в блоге Swift: Работа с JSON в Swift
================================================== =========================
В Swift 4+
Codable
протокол предоставляет более удобный способ разбора JSON непосредственно на структуры / классы.Например, данный образец JSON в вопросе (слегка изменен)
можно декодировать в структуру
Weather
. Типы Swift такие же, как описано выше. Есть несколько дополнительных опций:URL
можно декодировать напрямую какURL
.time
Целое число может быть декодировано , какDate
сdateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Другие кодируемые источники:
источник
dict!["currently"]!
к словарю, который компилятор может безопасно вывести для последующей подписки на ключ.Большое изменение, которое произошло с Xcode 8 Beta 6 для Swift 3, заключалось в том, что id теперь импортирует как,
Any
а неAnyObject
.Это означает, что
parsedData
он возвращается как словарь, скорее всего, с типом[Any:Any]
. Без использования отладчика я не мог бы точно сказать вам, чтоNSDictionary
будет делать ваше приведение , но ошибка, которую вы видите, связана с тем, чтоdict!["currently"]!
имеет типAny
Итак, как решить эту проблему? Судя по тому, как вы на него ссылались, я предполагаю,
dict!["currently"]!
что это словарь, поэтому у вас есть много вариантов:Сначала вы можете сделать что-то вроде этого:
Это даст вам объект словаря, который вы затем можете запросить для значений и, таким образом, вы можете получить свою температуру следующим образом:
Или, если хотите, можете сделать это в строке:
Надеюсь, это поможет, боюсь, у меня не было времени написать образец приложения для его тестирования.
И последнее замечание: проще всего было бы просто передать полезную нагрузку JSON в
[String: AnyObject]
самом начале.источник
dict!["currently"]! as! [String: String]
рухнет[String: String]
вообще не может работать, потому что есть пара числовых значений. Это не работает на игровой площадке Macисточник
Именно для этого я построил quicktype . Просто вставьте образец JSON, и quicktype сгенерирует эту иерархию типов для ваших данных API:
Он также генерирует код маршалинга без зависимостей, чтобы уговорить возвращаемое значение
JSONSerialization.jsonObject
в aForecast
, включая удобный конструктор, который принимает строку JSON, чтобы вы могли быстро проанализировать строго типизированноеForecast
значение и получить доступ к его полям:Вы можете установить quicktype из npm
npm i -g quicktype
или использовать веб-интерфейс, чтобы получить полный сгенерированный код для вставки на вашу игровую площадку.источник
Обновленный в
isConnectToNetwork-Function
потом, благодаря этому сообщению .Я написал для этого дополнительный метод:
Итак, теперь вы можете легко вызывать это в своем приложении, где хотите
источник
Swift имеет мощный вывод типов. Давайте избавимся от шаблонов «if let» или «guard let» и принудительно развернем развертывание, используя функциональный подход:
Всего одна строка кода и никаких принудительных развертываний или ручного преобразования типов. Этот код работает на игровой площадке, поэтому вы можете скопировать и проверить его. Вот реализация на GitHub.
источник
Это еще один способ решить вашу проблему. Поэтому ознакомьтесь с решением ниже. Надеюсь, это поможет тебе.
источник
Проблема в методе взаимодействия API. Разбор JSON изменен только в синтаксисе. Основная проблема связана со способом получения данных. Вы используете синхронный способ получения данных. Это работает не во всех случаях. Вам следует использовать асинхронный способ получения данных. Таким образом, вы должны запрашивать данные через API и ждать, пока он ответит данными. Вы можете добиться этого с помощью сеанса URL и сторонних библиотек, таких как
Alamofire
. Ниже приведен код метода URL-сеанса.источник
Если я создаю модель с использованием предыдущего json. Используя эту ссылку [блог]: http://www.jsoncafe.com для создания структуры Codable или Any Format
Модель
Анализировать
источник