Skip to content

Instantly share code, notes, and snippets.

@js
Created December 7, 2020 16:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save js/2fe70ed21ffc9c36d022fabc705c40ca to your computer and use it in GitHub Desktop.
Save js/2fe70ed21ffc9c36d022fabc705c40ca to your computer and use it in GitHub Desktop.
import UIKit
enum Item: Codable {
case int(Int)
case string(String)
private enum CodingKeys: String, CodingKey {
case kind
case value
}
struct UnknownKindDecodingError: Error {
let kindIdentifier: String
let context: DecodingError.Context
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kindIdentifier = try container.decode(String.self, forKey: .kind)
switch kindIdentifier { // this could well be a CodingKey as well, to remove the stringly typedness
case "string":
self = .string(try container.decode(String.self, forKey: .value))
case "int":
self = .int(try container.decode(Int.self, forKey: .value))
default:
throw UnknownKindDecodingError(kindIdentifier: kindIdentifier, context: .init(
codingPath: container.codingPath,
debugDescription: "Missing Item Kind"
))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .int(let value):
try container.encode("int", forKey: .kind)
try container.encode(value, forKey: .value)
case .string(let value):
try container.encode("string", forKey: .kind)
try container.encode(value, forKey: .value)
}
}
}
struct List: Codable {
var items: [Item]
init(items: [Item]) {
self.items = items
}
private enum CodingKeys: String, CodingKey {
case items
}
// Need something for nestedUnkeyedContainer to skip, as there's no next():
// https://forums.swift.org/t/pitch-unkeyeddecodingcontainer-movenext-to-skip-items-in-deserialization/22151
private struct Skippy: Codable {}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var itemsContainer = try container.nestedUnkeyedContainer(forKey: .items)
var items: [Item] = []
while !itemsContainer.isAtEnd {
do {
items.append(try itemsContainer.decode(Item.self))
} catch let error {
if let error = error as? Item.UnknownKindDecodingError {
print("Ignoring unknown type \(error.kindIdentifier)")
_ = try? itemsContainer.decode(Skippy.self)
} else {
throw error
}
}
}
self.items = items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(items, forKey: .items)
}
}
let json = """
{"items": [
{"kind": "int", "value": 42},
{"kind": "string", "value": "hello"}
]
}
"""
let jsonWithUnknown = """
{"items": [
{"kind": "int", "value": 42},
{"kind": "horse", "value": "moo"},
{"kind": "string", "value": "hello"},
]
}
"""
let list = List(items: [Item.int(3), .string("foo")])
let listJson = try JSONEncoder().encode(list)
print("encoded", String(data: listJson, encoding: .utf8)!)
let decoded = try JSONDecoder().decode(List.self, from: json.data(using: .utf8)!)
print("decoded", decoded.items)
do {
let decodedLossy = try JSONDecoder().decode(List.self, from: jsonWithUnknown.data(using: .utf8)!)
print("decoded lossy", decodedLossy.items)
} catch {
print("lossy decoding error", error)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment