Created
April 2, 2021 14:31
-
-
Save MaximKotliar/5f10a1d87832364ef3b027e6e7a0a0d4 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 UIKit | |
import SwiftUI | |
protocol AnyNode: AnyCodableType { | |
var id: UUID { get } | |
} | |
protocol AnyNodeContent: AnyCodableType {} | |
extension AnyNodeContent { | |
static func register() { | |
Node<Self>.register() | |
} | |
} | |
struct Node<Content: AnyNodeContent>: AnyNode { | |
var id: UUID = UUID() | |
var content: Content | |
} | |
struct Project: Codable { | |
let nodes: [AnyNode] | |
enum CodingKeys: String, CodingKey { | |
case nodes | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
nodes = try container.decode([AnyCodable].self, forKey: .nodes).map { $0.value as! AnyNode } | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
try container.encode(nodes.map { $0.wrapInAnyCodable() }, forKey: .nodes) | |
} | |
} | |
protocol AnyCodableType: Codable { | |
static var codingIdentifier: String { get } | |
func wrapInAnyCodable() -> AnyCodable | |
} | |
extension AnyCodableType { | |
static var codingIdentifier: String { String(describing: self) } | |
func wrapInAnyCodable() -> AnyCodable { .init(self) } | |
} | |
struct AnyCodable: Codable { | |
let typeIdentifier: String | |
let value: Any | |
enum CodingKeys: String, CodingKey { | |
case type | |
case value | |
} | |
init<T: AnyCodableType>(_ value: T) { | |
self.typeIdentifier = T.codingIdentifier | |
self.value = value | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
typeIdentifier = try container.decode(String.self, forKey: .type) | |
guard let decode = AnyCodable.codingFunctionsStorage[typeIdentifier]?.decode else { | |
throw Error.unregisteredType | |
} | |
var valueContainer = try container.nestedUnkeyedContainer(forKey: .value) | |
value = try decode(&valueContainer) | |
} | |
func encode(to encoder: Encoder) throws { | |
guard let encode = AnyCodable.codingFunctionsStorage[typeIdentifier]?.encode else { | |
throw Error.unregisteredType | |
} | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
var typeContainer = container.nestedUnkeyedContainer(forKey: .type) | |
var valueContainer = container.nestedUnkeyedContainer(forKey: .value) | |
try typeContainer.encode(typeIdentifier) | |
try encode(value, &valueContainer) | |
} | |
} | |
extension AnyCodable { | |
enum Error: Swift.Error { | |
case unregisteredType | |
} | |
static var codingFunctionsStorage: [String: CapturedCodingFunctions] = [:] | |
} | |
typealias CapturedCodingFunctions = (encode: (Any, inout UnkeyedEncodingContainer) throws -> Void, | |
decode: (inout UnkeyedDecodingContainer) throws -> Any) | |
extension AnyCodableType { | |
static func register() { | |
AnyCodable.codingFunctionsStorage[codingIdentifier] = (encode: { try $1.encode($0 as! Self) }, | |
decode: { try $0.decode(Self.self) }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment