Декодировать массив ключей JSON

У меня есть эти JSON данные:

{
  "cities": [
    {
      "id": 1,
      "name": "Paris",
      "country_code": "FR",
      "attractions": [
        "CityHall",
        "Theatre",
        "Port"
      ],
      "population": 0
    },
    {
      "id": 2,
      "name": "Nice",
      "country_code": "FR",
      "attractions": [
        "CityHall"
      ],
      "population": 0
    },
    {
      "id": 3,
      "name": "Berlin",
      "country_code": "DE",
      "attractions": [
        "Theatre",
        "Port"
      ],
      "population": 0
    },
    {
      "id": 4,
      "name": "Munich",
      "country_code": "DE",
      "attractions": [
        "Theatre"
      ],
      "population": 0
    },
    {
      "id": 5,
      "name": "Amsterdam",
      "country_code": "NL",
      "attractions": [
        "Theatre",
        "Port"
      ],
      "population": 0
    },
    {
      "id": 6,
      "name": "Leiden",
      "country_code": "NL",
      "attractions": [
        "CItyHall",
        "Theatre"
      ],
      "population": 0
    }
  ]
}

Я расшифровываю его с помощью Moya .request(.getCities).map([City].self, atKeyPath: "cities"), используя объект:

struct City {
  let id: Int
  let name: String
  let countryCode: String
  //let attractions: [Attraction]
  let population: Int
}

extension City: Decodable  {
  enum CodingKeys: String, CodingKey {
    case id = "id"
    case name = "name"
    case countryCode = "countryCode"
    //case attractions = "attractions"
    case population = "population"
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    id = try container.decode(Int.self, forKey: .id)
    name = try container.decode(String.self, forKey: .name)
    countryCode = try container.decode(String.self, forKey: .countryCode)
    //attractions = try container.decode([Attraction].self, forKey: .attractions)
    population = try container.decode(Int.self, forKey: .population)
  }
}

Просто и красиво, но проблема в том, что я не могу понять, как вписать сюда этот массив attractions. У меня это как enum и я пытался получить ключи с помощью codingPath

enum Attraction: String {
    case CityHall
    case Theatre
    case Port
}

extension Attraction: Decodable  {
  enum CodingKeys: String, CodingKey {

    // confusion mode initiated :) do I use it the same as in struct here?
    case cityHall = "CityHall"
    case theatre = "Theatre"
    case port = "Port"
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    
    let attraction = container.codingPath.first!.stringValue // that would get the first key into string but how to deal with enum?
  }
}

Кроме того, если это будет нормально декодировать attractions, будет ли объект City нормально декодировать вложенный объект/массив?


person Maks Shevchenko    schedule 26.09.2020    source источник
comment
atractions в вашем JSON написано с ошибкой. Вы можете что-нибудь с этим сделать?   -  person Sweeper    schedule 26.09.2020


Ответы (1)


Просто и красиво

Нет, просто и красиво

struct Root : Decodable {
    let cities : [City]
}

struct City : Decodable {
  let id: Int
  let name: String
  let countryCode: String
  let atractions: [Attraction] // note the misspelling
  let population: Int
}

enum Attraction: String, Decodable {
    case cityHall = "CityHall", theatre = "Theatre", port = "Port"
}

let data = Data(jsonString.utf8)

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(Root.self, from: data)
    print(result.cities)
} catch {
    print(error)
}
person vadian    schedule 26.09.2020
comment
Судя по строке Декодирование с помощью .map([CIty].self, atKeyPath: "cities"), ОП, похоже, использует Мойю, и в этом случае декодирование теперь будет выглядеть как map([City].self, atKeyPath: "cities", using: decoder), и вам не понадобится Root. - person Sweeper; 26.09.2020
comment
@Sweeper Спасибо, да, но ОП должен где-то упомянуть об этом. Я просто хотел предоставить простое и красивое решение. - person vadian; 26.09.2020
comment
Я понимаю. Мой комментарий в основном ради ОП. - person Sweeper; 26.09.2020
comment
Большое спасибо, @vadian! Это действительно выглядит просто и красиво :) Вы не использовали init(from decoder:. Вот почему..? Также да, я использую Moya, но, судя по вашему ответу, мне нужно указать только строковые значения для перечисления, и оно будет декодируемым. Попробую это сейчас - person Maks Shevchenko; 26.09.2020
comment
Я не использую init(from decoder:, потому что вы получаете синтезированную стандартную реализацию бесплатно. - person vadian; 26.09.2020