#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 или что-то в этом роде. #ментальный блок