Last active
November 23, 2017 13:31
-
-
Save paulofaria/339ffa172cc84b2725f2b5a598f4fda9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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