Skip to content

Instantly share code, notes, and snippets.

@paulofaria
Last active November 23, 2017 13:31
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 paulofaria/339ffa172cc84b2725f2b5a598f4fda9 to your computer and use it in GitHub Desktop.
Save paulofaria/339ffa172cc84b2725f2b5a598f4fda9 to your computer and use it in GitHub Desktop.
import Foundation
extension String : CodingKey {
public var stringValue: String {
return self
}
public var intValue: Int? {
return Int(self)
}
public init?(stringValue: String) {
self = stringValue
}
public init?(intValue: Int) {
self = intValue.description
}
}
enum TypeCodableError : Error {
case typeIsNotSubtype(subtype: Decodable.Type, supertype: Any.Type)
case noTypeForSubtypeName(subtypeName: String)
case valueIsNotEncodable(value: Any)
case valueIsNotSubtype(value: Any, supertype: Any.Type)
}
protocol TypeCodable : Codable {
associatedtype Supertype
var value: Supertype { get }
init(_ value: Supertype)
static var subtypes: [String: Codable.Type] { get }
static var subtypeKey: String { get }
}
extension TypeCodable {
static var subtypeKey: String {
return "type"
}
static func decode(from decoder: Decoder) throws -> Supertype {
let container = try decoder.container(keyedBy: String.self)
let subtypeName = try container.decode(String.self, forKey: subtypeKey)
for (name, subtype) in subtypes where name == subtypeName {
guard let value = try subtype.init(from: decoder) as? Supertype else {
throw TypeCodableError.typeIsNotSubtype(subtype: subtype, supertype: Supertype.self)
}
return value
}
throw TypeCodableError.noTypeForSubtypeName(subtypeName: subtypeName)
}
static func encode(_ value: Any, to encoder: Encoder) throws {
var container = encoder.container(keyedBy: String.self)
for (name, subtype) in subtypes where subtype == type(of: value) {
try container.encode(name, forKey: subtypeKey)
guard let encodableValue = value as? Encodable else {
throw TypeCodableError.valueIsNotEncodable(value: value)
}
return try encodableValue.encode(to: encoder)
}
throw TypeCodableError.valueIsNotSubtype(value: value, supertype: Supertype.self)
}
init(from decoder: Decoder) throws {
let value = try Self.decode(from: decoder)
self.init(value)
}
func encode(to encoder: Encoder) throws {
try Self.encode(value, to: encoder)
}
}
extension KeyedDecodingContainer {
func decode<Info>(
_ type: Info.Supertype.Type,
forKey key: KeyedDecodingContainer.Key,
using info: Info.Type = Info.self
) throws -> Info.Supertype where Info : TypeCodable {
return try decode(Info.self, forKey: key).value
}
func decode<Info>(
_ type: [Info.Supertype].Type,
forKey key: KeyedDecodingContainer.Key,
using info: Info.Type = Info.self
) throws -> [Info.Supertype] where Info : TypeCodable {
return try decode([Info].self, forKey: key).map({ $0.value })
}
func decode<Info>(
_ type: (Info.Supertype?).Type,
forKey key: KeyedDecodingContainer.Key,
using info: Info.Type = Info.self
) throws -> Info.Supertype? where Info : TypeCodable {
do {
return try decode((Info?).self, forKey: key).map({ $0.value })
} catch DecodingError.keyNotFound {
return nil
}
}
}
extension KeyedEncodingContainer {
mutating func encode<Info>(
_ value: Info.Supertype,
forKey key: KeyedEncodingContainer.Key,
using info: Info.Type = Info.self
) throws where Info : TypeCodable {
try encode(Info(value), forKey: key)
}
mutating func encode<Info>(
_ value: [Info.Supertype],
forKey key: KeyedEncodingContainer.Key,
using info: Info.Type = Info.self
) throws where Info : TypeCodable {
try encode(value.map({ Info($0) }), forKey: key)
}
mutating func encode<Info>(
_ value: Info.Supertype?,
forKey key: KeyedEncodingContainer.Key,
using info: Info.Type = Info.self
) throws where Info : TypeCodable {
guard let value = value else {
return
}
try encode(Info(value), forKey: key)
}
}
// -------------------------------------------------------------------
struct ProductTypeCodable : TypeCodable {
static let subtypes: [String: Codable.Type] = [
"single": Single.self,
"bundle": Bundle.self
]
let value: Product
init(_ value: Product) {
self.value = value
}
}
protocol Product {
var id: String { get }
}
struct Bundle : Product, Codable {
let id: String
let item: String
}
struct Single : Product, Codable {
let id: String
let quantity: Int
}
struct Order : Codable {
let bestProduct: Product
let worstProduct: Product?
let products: [Product]
enum CodingKeys : String, CodingKey {
case bestProduct = "best-product"
case worstProduct = "worst-product"
case products
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bestProduct = try container.decode(Product.self, forKey: .bestProduct, using: ProductTypeCodable.self)
worstProduct = try container.decode((Product?).self, forKey: .worstProduct, using: ProductTypeCodable.self)
products = try container.decode([Product].self, forKey: .products, using: ProductTypeCodable.self)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(bestProduct, forKey: .bestProduct, using: ProductTypeCodable.self)
try container.encode(worstProduct, forKey: .worstProduct, using: ProductTypeCodable.self)
try container.encode(products, forKey: .products, using: ProductTypeCodable.self)
}
}
let decoder = JSONDecoder()
let encoder = JSONEncoder()
let json =
"""
{
"best-product": {
"type": "single",
"id": "best",
"quantity": 42
},
"products": [
{
"type": "single",
"id": "single",
"quantity": 69
},
{
"type": "bundle",
"id": "bundle",
"item": "combo"
}
]
}
"""
let order = try decoder.decode(Order.self, from: json.data(using: .utf8)!)
print(order)
let string = String(data: try encoder.encode(order), encoding: .utf8)
print(string!)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment