Конкретная проблема синтаксического анализа JSON — массив различных объектов — Swift

#ios #json #swift #parsing

Вопрос:

У меня есть блок JSON, который я должен использовать. Я не могу контролировать форму данных JSON.

Допустим, у меня есть большой двоичный объект ответа, который выглядит так:

 {
"resultStatus" : 1,
"resultEntities" : [...]
}
 

Внутри массива resultEntities есть два разных объекта; один тип всегда имеет индекс 0, по сути, заголовок для всего, что следует за ним, а индексы 1…-> содержат другой тип (я могу управлять типом, который я прошу). Между этими двумя объектами есть некоторое перекрытие полей, но только несколько полей из общего числа около 30 полей.

 {
"rectype" : 1,
"recname" : "header",
"companyname" : "Smithson amp; Jones",
"companyId" : "q1w2e3r4",
...
}
 

и

 {
"rectype" : 2,
"recname" : "detail record",
"locationId" : "123 Miami Warehouse",
"shelvingUnits" : 654,
...
}
 

Мой принимающий объект выглядит в основном так:

 struct APIResponse : Decodable {
let resultStatus : Int
let results : [...] //<--- and there is the issue
 

Я не думаю, что смогу определить свой принимающий объект так, чтобы результаты[0] всегда пытались анализировать в заголовок, а все остальные анализировались до деталей, верно?

Я, очевидно, не могу сделать что — то подобное (псевдокод, я знаю, что это не будет компилироваться-это просто для того, чтобы прояснить, с чем я имею дело):

 let results : [ 0 = header type, ... = detail type ]
 

или

 let results[0] : Header 
let results[...] : Detail
 

и так далее.

Итак, должен ли объект, являющийся массивом в результатах, быть просто объединением заголовка и сведений, при этом все поля (кроме известных перекрытий) должны быть необязательными?

Надеюсь, я объясняю это достаточно хорошо.

Мысли? (с удовольствием отвечу на любые вопросы, чтобы при необходимости уточнить детали и соответствующим образом обновить вопрос)

Комментарии:

1. Вам нужен пользователь init(from decoder) для работы с вашим конкретным случаем. Но определяется ли «Заголовок» или что-то другое, например, в соответствии со rectype значением? Это может быть одним из способов их различения… И в идеале вы хотели бы: let header: Header; let other: Other , т. е. два разных var в ответе? Как должна выглядеть ваша идеальная структура?

2. Не уверен, правильно ли я понял, у вас есть заголовок и тип сведений в массиве или это один тип заголовка и один из многих типов сведений?

3. Прямой тип (и общая форма), но да, кажется, я могу рассчитывать на то, что это фиксированные значения для типов. Там есть кое-что… хм… текучесть между документами, которые у меня есть, и фактическими данными, которые я получаю до сих пор. Но это «достаточно близко для правительственной работы». 🙂 Я возвращаю объект данных из вызова API, и я бы предпочел не разбирать весь текст в json. Это глупо просто в чем-то вроде JavaScript, но переход от объекта данных к чему-то проходимому без использования объекта синтаксического анализа кажется либо невозможным, либо неочевидным-это документы.

4. @JoakimDanielson, в результатах индекс 0 всегда является заголовком, а индексы за его пределами являются типом сведений. Тип сведений всегда один и тот же, независимо от количества записей.

5. @ChrisH, следуя вашему предпоследнему комментарию, Swift как бы предоставляет эту функциональность, хотя и по-другому, используя перечисление со связанными значениями. например enum HeaderDetail, case header(Header), case detail (Detail) , а затем определяя, какой вариант перечисления находится в 'init(from decoder) , а затем в вашем let results: [HeaderDetail] . Это обеспечивает строгое соответствие типу при одновременном использовании различных полезных нагрузок.

Ответ №1:

Вот решение, использующее только Codable , с пользовательским init(from:) интерфейсом, в котором мы используем контейнер unkeyed для декодирования содержимого массива. Обратите внимание, что значение заголовка было перемещено из массива в его собственное свойство в Result структуре

 struct Result: Decodable {
  let status: Int
  let header: Header
  var entities: [DetailRecord]
  
  enum CodingKeys: String, CodingKey {
    case status = "resultStatus"
    case entities = "resultEntities"
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    status = try container.decode(Int.self, forKey: .status)
    var nested = try container.nestedUnkeyedContainer(forKey: .entities)
    header = try nested.decode(Header.self)
    
    entities = []
    while !nested.isAtEnd {
      let detail = try nested.decode(DetailRecord.self)
      entities.append(detail)
    }
  }
}
 

Если вам просто нужно быстрое и грязное решение, вы можете использовать JSONSerialzation

 do {
  let dictionary = try JSONSerialization.jsonObject(with: data) as! [String: Any]
  
  if let entities = dictionary["resultEntities"] as? [Any], !entities.isEmpty {
    let header = entities.first!
      print(header)
    
    let details = Array(entities.dropFirst())
    print(details)
  }
} catch {
  print(error)
}
 

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

Один из способов использования Codable здесь состоит в том, чтобы сначала преобразовать header details переменные и в объект данных json, а затем использовать декодер для преобразования их в соответствующие объекты

 let detailsData = try JSONSerialization.data(withJSONObject: details)
let headerData = try JSONSerialization.data(withJSONObject: header)

let decoder = JSONDecoder()
let headerObject = try decoder.decode(Header.self, from: headerData)
let detailsArray = try decoder.decode([DetailRecord].self, from: detailsData)
 

Комментарии:

1. Потрясающе. Я просто смотрел на JSONSerialization, прежде чем вернуться к этому. Большое спасибо! Это в значительной степени подтверждает это. если я смогу извлечь данные для каждого элемента, я могу попробовать-проанализировать/попробовать-десериализовать их и использовать объекты, однажды сформированные из этого. Большое спасибо. Отмечая это как принятый ответ. Это именно то, что я искал, но я не видел связи. Я думаю, я предполагал, что JSONSerialization была просто более старой версией декодера json или что-то в этом роде. #ментальный блок