При использовании протоколов Swift4 и Codable у меня возникла следующая проблема - похоже, что нет возможности JSONDecoder
пропустить элементы в массиве. Например, у меня есть следующий JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
И структура Codable :
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
При декодировании этого json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Результат products
пуст. Этого и следовало ожидать, поскольку второй объект в JSON не имеет "points"
ключа, а points
в GroceryProduct
struct не является обязательным .
Вопрос в том, как я могу позволить JSONDecoder
«пропустить» недопустимый объект?
points
просто объявить необязательным?Ответы:
Один из вариантов - использовать тип-оболочку, который пытается декодировать заданное значение; сохранение в
nil
случае неудачи:Затем мы можем декодировать их массив,
GroceryProduct
заполнивBase
заполнитель:Затем мы используем,
.compactMap { $0.base }
чтобы отфильтроватьnil
элементы (те, которые вызвали ошибку при декодировании).Это создаст промежуточный массив
[FailableDecodable<GroceryProduct>]
, что не должно быть проблемой; однако, если вы хотите избежать этого, вы всегда можете создать другой тип оболочки, который декодирует и разворачивает каждый элемент из неключевого контейнера:Затем вы бы декодировали как:
источник
var container = try decoder.unkeyedContainer()
init(from:) throws
, поэтому Swift автоматически передаст ошибку обратно вызывающему (в данном случае декодеру, который передаст ее обратноJSONDecoder.decode(_:from:)
вызову).Я бы создал новый тип
Throwable
, который может обернуть любой тип, соответствующийDecodable
:Для декодирования массива
GroceryProduct
(или любого другогоCollection
):где
value
- вычисляемое свойство, введенное в расширении наThrowable
:Я бы предпочел использовать
enum
тип оболочки (вместо aStruct
), потому что может быть полезно отслеживать возникающие ошибки, а также их индексы.Swift 5
Для Swift 5 рассмотрите возможность использования eg
Result
enum
Чтобы развернуть декодированное значение, используйте
get()
методresult
свойства:источник
init
Проблема в том, что при итерации по контейнеру container.currentIndex не увеличивается, поэтому вы можете снова попытаться декодировать с другим типом.
Поскольку currentIndex доступен только для чтения, решение состоит в том, чтобы увеличить его самостоятельно, успешно декодировав пустышку. Я взял решение @Hamish и написал обертку с собственным init.
Это текущая ошибка Swift: https://bugs.swift.org/browse/SR-5953
Опубликованное здесь решение - это обходной путь в одном из комментариев. Мне нравится этот вариант, потому что я таким же образом разбираю кучу моделей на сетевом клиенте, и я хотел, чтобы решение было локальным для одного из объектов. То есть я все же хочу, чтобы остальные выбросили.
Я объясню лучше в моем github https://github.com/phynet/Lossy-array-decode-swift4
источник
if/else
использоватьdo/catch
внутриwhile
цикла, чтобы я мог регистрировать ошибкуЕсть два варианта:
Объявить все члены структуры как необязательные, ключи которых могут отсутствовать
Напишите собственный инициализатор для присвоения значений по умолчанию в
nil
случае.источник
try?
сdecode
лучше использоватьtry
сdecodeIfPresent
во втором варианте. Нам нужно установить значение по умолчанию только в том случае, если ключа нет, а не в случае сбоя декодирования, например, когда ключ существует, но тип неправильный.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
поэтому, если он не удастся, просто вставит 0000, но он все равно не удастся.decodeIfPresent
это неправильно,API
потому что ключ действительно существует. Используйте другойdo - catch
блок. РасшифроватьString
, если возникнет ошибка, расшифроватьInt
Решение стало возможным благодаря Swift 5.1 с использованием оболочки свойств:
А затем использование:
Примечание. Обертка свойств будет работать только в том случае, если ответ может быть заключен в структуру (т.е. не в массив верхнего уровня). В этом случае вы все равно можете обернуть его вручную (с typealias для лучшей читаемости):
источник
Я поместил решение @ sophy-swicz с некоторыми изменениями в простое в использовании расширение
Просто назови это так
В приведенном выше примере:
источник
К сожалению, Swift 4 API не имеет ошибочного инициализатора для
init(from: Decoder)
.Только одно решение, которое я вижу, реализует пользовательское декодирование, предоставляя значение по умолчанию для дополнительных полей и возможный фильтр с необходимыми данными:
источник
Недавно у меня была похожая проблема, но немного другая.
В этом случае, если один из элементов
friendnamesArray
равен нулю, весь объект при декодировании равен нулю.И правильный способ справиться с этим пограничным случаем - объявить массив строк
[String]
как массив необязательных строк,[String?]
как показано ниже:источник
Я улучшил @ Hamish для случая, когда вы хотите, чтобы такое поведение было для всех массивов:
источник
Вместо этого вы также можете сделать так:
а затем при получении:
источник
@ Ответ Хэмиша отличный. Однако вы можете сократить
FailableCodableArray
до:источник
Я придумал такой
KeyedDecodingContainer.safelyDecodeArray
простой интерфейс:Потенциально бесконечный цикл
while !container.isAtEnd
вызывает беспокойство, и его можно решить с помощьюEmptyDecodable
.источник
Гораздо более простая попытка: почему бы вам не объявить точки как необязательные или не сделать массив, содержащий необязательные элементы
источник