Декодирование JSON для одного объекта против массива объектов в Swift - PullRequest
0 голосов
/ 20 марта 2020

Я довольно плохо знаком с быстрым языком программирования и пытался заставить его работать в течение последней недели или около того. Я работаю с существующим API, который возвращает JSON данные, структура которых немного меняется в зависимости от того, сколько мест возвращается.

Реальная структура несколько сложнее, но этот пример иллюстрирует проблему. В одном варианте результата я получаю одно возвращаемое место, например:

{
  "totalItems": 21,
  "pageSize": 2,
  "venues": {
    "venue":
    {
       "name": "Venue Name 1"
       "location": "Location A",
       "showCount": "4"
    }
  }
}

В другом варианте результата я получаю массив возвращаемых мест:

{
  "totalItems": 21,
  "pageSize": 2,
  "venues": {
    "venue":
    [{
       "name": "Venue Name 1"
       "location": "Location A",
       "showCount": "4"
    },
    {
       "name": "Venue Name 2"
       "location": "Location B",
       "showCount": "2"
    }]
  }
}

Да - владелец этого API должен был возвратить массив независимо, но они этого не сделали, и его нельзя изменить.

Мне удалось заставить его правильно декодироваться для массива мест (или даже если места не были переданы) , но оно прерывается при прохождении одного места (из-за изменения структуры, конечно). Мой код также работал, когда я изменил его для размещения одного места, но затем прервалось возвращение нескольких мест.

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

struct Response: Decodable {
    let totalItems: Int
    let pageSize: Int
    let venues: VenueWrapper

    struct VenueWrapper: Decodable {
        let venue: [Venue]     // This might contain 0, 1, or more than one venues
    }

    struct Venue: Decodable {
        let name: String
        let location: String
        let showCount: Int
    }
}

Примечание: в фактическом ответе JSON в ответе есть несколько таких подструктур, как эта (например, одна структура против массива структур), поэтому я почувствовал простое создание альтернативной структуры не было хорошим решением.

Я надеюсь, что кто-то сталкивался с этим раньше. Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 20 марта 2020

Нет необходимости в VenueWrapper. 100

struct Response {
  let totalItems: Int
  let pageSize: Int
  let venues: [Venue]

  struct Venue {
    let name: String
    let location: String
    let showCount: Int
  }
}

Вам нужно написать свой собственный инициализатор. К сожалению, я не думаю, что есть способ исключить шаблон для свойств, не закодированных глупостью.

Даже если вам никогда не нужно кодировать его, получая Response Codable, а не просто Decodable, даст вам доступ к автоматически сгенерированным CodingKeys.

extension Response: Codable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    totalItems = try container.decode(Int.self, forKey: .totalItems)
    pageSize = try container.decode(Int.self, forKey: .pageSize)
    venues = try container.decode(key: .venues)
  }
}

Эта последняя строка опирается на протокол и расширение, которые вы можете использовать для любых других типов, которые аналогично «закодированы». 101

protocol GoofilyEncoded: Codable {
  /// Must have exactly one case.
  associatedtype GoofyCodingKey: CodingKey
}

extension KeyedDecodingContainer {
  func decode<Decodable: GoofilyEncoded>(key: Key) throws -> [Decodable] {
    let nestedContainer = try self.nestedContainer(
      keyedBy: Decodable.GoofyCodingKey.self,
      forKey: key
    )

    let key = nestedContainer.allKeys.first!

    do {
      return try nestedContainer.decode([Decodable].self, forKey: key)
    }
    catch {
      return try [nestedContainer.decode(Decodable.self, forKey: key)]
    }
  }
}

Для всех типов, которые могут быть закодированы в массивах, или нет ?‍♂️, потребуется перечисление в одном регистре, например:

extension Response.Venue: GoofilyEncoded {
  enum GoofyCodingKey: CodingKey {
    case venue
  }
}
0 голосов
/ 20 марта 2020

Вы можете создать свой собственный декодер,

struct Response: Decodable {
    let totalItems: Int
    let pageSize: Int
    let venues: VenueWrapper

    struct VenueWrapper: Decodable {
        var venue: [Venue]

        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            venue = []
            if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
                //if a single venue decoded append it to array
                venue.append(singleVenue)
            } else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
                //if a multi venue decoded, set it as venue
                venue = multiVenue
            }

            enum CodingKeys: String, CodingKey { case venue }
        }
    }

    struct Venue: Decodable {
        let name: String
        let location: String
        let showCount: String
    }
}
...