UnkeyedDecodingContainer.moveNext() to skip items in deserialization
Using JSONDecoder
if you need to deserialize an heterogeneous array containing classes of multiple types, you use UnkeyedDecodingContainer
in a code like this
struct Feed: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FeedKeys.self)
var messagesArrayForType = try container.nestedUnkeyedContainer(forKey: FeedKeys.messages)
var messages = [Message]()
var messagesArray = messagesArrayForType
while(!messagesArrayForType.isAtEnd)
{
let message = try messagesArrayForType.nestedContainer(keyedBy: MessageTypeKey.self)
let type = try message.decode(String.self, forKey: MessageTypeKey.type)
switch type {
case .avatar:
messages.append(try messagesArray.decode(AvatarMessage.self))
case .add:
messages.append(try messagesArray.decode(AddMessage.self))
}
}
self.messages = messages
}
}
The problem
The problem is when you decide to ignore an element of the JSON array
struct Feed: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FeedKeys.self)
var messagesArrayForType = try container.nestedUnkeyedContainer(forKey: FeedKeys.messages)
var messages = [Message]()
var messagesArray = messagesArrayForType
while(!messagesArrayForType.isAtEnd)
{
let message = try messagesArrayForType.nestedContainer(keyedBy: MessageTypeKey.self)
let type = try message.decode(String.self, forKey: MessageTypeKey.type)
switch type {
case .avatar:
messages.append(try messagesArray.decode(AvatarMessage.self))
case .add:
messages.append(try messagesArray.decode(AddMessage.self))
case .remove:
// skip, no longer needed in the app
// how to move to the next item in the JSON array?
}
}
self.messages = messages
}
}
There is currently no way to skip an item in the JSON array when you do not need it for some reason.
Current workarounds
The best thing you can currently do is to create some kind of a dummy class
private struct DummyCodable: Codable {}
and use it for all the items you want to skip
struct Feed: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FeedKeys.self)
var messagesArrayForType = try container.nestedUnkeyedContainer(forKey: FeedKeys.messages)
var messages = [Message]()
var messagesArray = messagesArrayForType
while(!messagesArrayForType.isAtEnd)
{
let message = try messagesArrayForType.nestedContainer(keyedBy: MessageTypeKey.self)
let type = try message.decode(String.self, forKey: MessageTypeKey.type)
switch type {
case .avatar:
messages.append(try messagesArray.decode(AvatarMessage.self))
case .add:
messages.append(try messagesArray.decode(AddMessage.self))
case .remove:
_ = try? messagesArray.decode(DummyCodable.self)
}
}
self.messages = messages
}
}
Proposed solution
A better solution would be to add UnkeyedDecodingContainer.moveNext()
; a new method that moves the index by 1 item, so the there is no need for a workaround
struct Feed: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FeedKeys.self)
var messagesArrayForType = try container.nestedUnkeyedContainer(forKey: FeedKeys.messages)
var messages = [Message]()
var messagesArray = messagesArrayForType
while(!messagesArrayForType.isAtEnd)
{
let message = try messagesArrayForType.nestedContainer(keyedBy: MessageTypeKey.self)
let type = try message.decode(String.self, forKey: MessageTypeKey.type)
switch type {
case .avatar:
messages.append(try messagesArray.decode(AvatarMessage.self))
case .add:
messages.append(try messagesArray.decode(AddMessage.self))
case .remove:
messagesArray.moveNext()
}
}
self.messages = messages
}
}
Other uses
This new method could be also useful if you want to ignore incomplete data
struct Feed: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FeedKeys.self)
var messagesArrayForType = try container.nestedUnkeyedContainer(forKey: FeedKeys.messages)
var messages = [Message]()
var messagesArray = messagesArrayForType
while(!messagesArrayForType.isAtEnd)
{
if let message = try? messagesArray.decode(Message.self) {
messages.append(message)
} else {
messagesArray.moveNext()
}
}
self.messages = messages
}
}
Basically adding a way to solve [SR-5953] Decodable: Allow "Lossy" Array Decodes · Issue #4414 · apple/swift-corelibs-foundation · GitHub