Использование decodable для анализа данных из двух разных контейнеров в одном json - swift

Я хотел знать, можно ли анализировать данные из файла json для создания типа, в котором файл json хранит несколько объектов того типа, который вы хотите создать, в более чем одном месте в структуре данных json. Я имею в виду, используя приведенный ниже пример json. Я хочу создать два объекта «Погода». один из контейнера "В настоящее время" и один из контейнера "ежедневно"> "Данные". Я опубликую свой код ниже, который работает для получения «текущих» данных, но я не уверен, как использовать ту же структуру Weather для последующего получения данных из ежедневного контейнера. Я хочу получить один массив типа Weather с двумя объектами: один из контейнеров «сейчас» и один из контейнеров «ежедневно».

p.s. Я понимаю, что один из способов решить эту проблему - иметь две разные структуры или два разных класса погоды, один для текущего и другого для прогноза. Предположим, это может быть лучше для определения того, что такое данные, но мне было бы интересно узнать, возможно ли то, что я описываю выше.

{
"currently": {
    "time": 1520250330,
    "summary": "Clear",
    "icon": "clear-night",
    "nearestStormDistance": 198,
    "nearestStormBearing": 16,
    "precipIntensity": 0,
    "precipProbability": 0,
    "temperature": 41.42,
    "apparentTemperature": 41.42,
    "dewPoint": 33.95,
    "humidity": 0.75,
    "pressure": 1027.07,
    "windSpeed": 2.89,
    "windGust": 5.43,
    "windBearing": 39,
    "cloudCover": 0,
    "uvIndex": 0,
    "visibility": 10,
    "ozone": 346.58
},
"daily": {
    "summary": "Light rain on Wednesday, with temperatures falling to 58°F on Sunday.",
    "icon": "rain",
    "data": [
        {
            "time": 1520236800,
            "summary": "Partly cloudy overnight.",
            "icon": "partly-cloudy-night",
            "sunriseTime": 1520260588,
            "sunsetTime": 1520302114,
            "moonPhase": 0.64,
            "precipIntensity": 0.0002,
            "precipIntensityMax": 0.0018,
            "precipIntensityMaxTime": 1520283600,
            "precipProbability": 0.13,
            "precipType": "rain",
            "temperatureHigh": 58.02,
            "temperatureHighTime": 1520298000,
            "temperatureLow": 46.48,
            "temperatureLowTime": 1520348400,
            "apparentTemperatureHigh": 58.02,
            "apparentTemperatureHighTime": 1520298000,
            "apparentTemperatureLow": 42.09,
            "apparentTemperatureLowTime": 1520348400,
            "dewPoint": 36.4,
            "humidity": 0.62,
            "pressure": 1024.6,
            "windSpeed": 4.65,
            "windGust": 15.36,
            "windGustTime": 1520269200,
            "windBearing": 35,
            "cloudCover": 0.08,
            "uvIndex": 5,
            "uvIndexTime": 1520280000,
            "visibility": 10,
            "ozone": 340.2,
            "temperatureMin": 41.36,
            "temperatureMinTime": 1520254800,
            "temperatureMax": 58.02,
            "temperatureMaxTime": 1520298000,
            "apparentTemperatureMin": 37.53,
            "apparentTemperatureMinTime": 1520262000,
            "apparentTemperatureMax": 58.02,
            "apparentTemperatureMaxTime": 1520298000
        }

Текущая структура:

struct Weather: Decodable {
let temperature: Double
var temperatureCelsius: Double {
    let temp = 5 / 9 * (temperature - 32) as Double
    return Double(round(temp))
}
let humidity: Double
let rainProbability: Int
let summary: String
let icon: String
let temperatureMax: Double?
let temperatureMin: Double?

private enum CodingKeys: String, CodingKey {
    case temperature
    case humidity
    case rainProbability = "precipProbability"
    case summary
    case icon
    case temperatureMax
    case temperatureMin
}

private enum CurrentlyKeys: String, CodingKey {
    case currently
}

public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CurrentlyKeys.self)
    let weatherValues = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .currently)
    temperature = try weatherValues.decode(Double.self, forKey: .temperature)
    humidity = try weatherValues.decode(Double.self, forKey: .humidity)
    rainProbability = try weatherValues.decode(Int.self, forKey: .rainProbability)
    summary = try weatherValues.decode(String.self, forKey: .summary)
    icon = try weatherValues.decode(String.self, forKey: .icon)
    temperatureMax = try weatherValues.decodeIfPresent(Double.self, forKey: .temperatureMax)
    temperatureMin = try weatherValues.decodeIfPresent(Double.self, forKey: .temperatureMin)
}

Разбор данных:

do {
      let currentWeather = try JSONDecoder().decode(Weather.self, from: data)
      print(currentWeather.summary)
    } catch let error {
      print("error: \(error)")
    }

person Angus.M    schedule 05.03.2018    source источник
comment
Если вы хотите повторно использовать словарь данных о погоде, вам нужно создать отдельную структуру вместо синтаксического анализа nestedContainer   -  person vadian    schedule 05.03.2018


Ответы (1)


Один из способов решить эту проблему, сохраняя при этом преимущества простого использования Codable, - это создать тип, соответствующий полученному вами ответу, который имеет необходимые вам свойства (объект Weather для currentWeather и массив объектов Weather для прогноза ).

В этом конкретном случае это немного сложнее, потому что массив прогнозов заключен в еще один объект JSON. Вы можете решить эту проблему, предоставив собственную реализацию декодирования вместо использования, созданной Codable:

struct WeatherApiResponse: Decodable {

    fileprivate struct DailyContainer: Decodable {
        let data: [Weather]
    }

    let currentWeather: Weather
    let dailyWeather: [Weather]

    init(from decoder: Decoder) throws {
        let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
        currentWeather = try keyedContainer.decode(Weather.self, forKey: .currentWeather)
        dailyWeather = (try keyedContainer.decode(DailyContainer.self, forKey: .dailyWeather)).data
    }

    enum CodingKeys: String, CodingKey {
        case currentWeather = "current"
        case dailyWeather = "daily"
    }
}

И расшифруйте два необходимых вам свойства следующим образом:

if let response = try? jsonDecoder.decode(WeatherApiResponse.self, from: data) {
    print(response.currentWeather)
    print(response.dailyWeather)
}
person l_priebe    schedule 05.03.2018
comment
Спасибо за подробный ответ. Только что попробовали этот подход, и он хорошо работает. Нашел очень интересными данные `(попробуйте keyedContainer.decode (DailyContainer.self, forKey: .dailyWeather)). Следует помнить! - person Angus.M; 06.03.2018