Skip to content

Instantly share code, notes, and snippets.

@hamishknight
Last active June 6, 2019 12:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hamishknight/5ffe87a43590a1f1fae8e341cf0da418 to your computer and use it in GitHub Desktop.
Save hamishknight/5ffe87a43590a1f1fae8e341cf0da418 to your computer and use it in GitHub Desktop.
import Foundation
protocol Tag : Codable {
associatedtype T
static var type: TagType { get }
var value: String { get }
var somethingElse: T { get }
}
enum TagType : String, Codable {
// be careful not to rename these – the encoding/decoding relies on the string
// values of the cases. If you want the decoding to be reliant on case
// position rather than name, then you can change to enum TagType : Int.
// (the advantage of the String rawValue is that the JSON is more readable)
case author, genre
}
struct AuthorTag : Tag {
static var type = TagType.author
let value: String
var foo: Float
var somethingElse: Double
}
struct GenreTag : Tag {
static var type = TagType.genre
let value: String
var baz: String
var somethingElse: Bool
}
fileprivate class _AnyTagBase : Encodable {
var value: String { fatalError() }
var somethingElse: Any { fatalError() }
var type: TagType { fatalError() }
func encode(to encoder: Encoder) throws {
fatalError()
}
}
fileprivate final class _AnyTagBox<Base : Tag> : _AnyTagBase, CustomStringConvertible {
var base: Base
init(_ base: Base) {
self.base = base
}
override var type: TagType { return Base.type }
override var value: String { return base.value }
override var somethingElse: Any { return base.somethingElse }
override func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(base)
}
var description: String {
return String(describing: base)
}
}
struct AnyTag : Codable {
private let base: _AnyTagBase
var value: String { return base.value }
var somethingElse: Any { return base.somethingElse }
var type: TagType { return base.type }
init<Base : Tag>(_ base: Base) {
self.base = _AnyTagBox(base)
}
func unbox<Base : Tag>(as _: Base.Type) -> Base? {
guard let unboxedBase = base as? _AnyTagBox<Base> else { return nil }
return unboxedBase.base
}
private enum CodingKeys : CodingKey {
case type, base
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TagType.self, forKey: .type)
switch type {
case .author:
self.init(try container.decode(AuthorTag.self, forKey: .base))
case .genre:
self.init(try container.decode(GenreTag.self, forKey: .base))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(base.type, forKey: .type)
try container.encode(base, forKey: .base)
}
}
struct Article : Codable {
let tags: [AnyTag]
let title: String
init(tags: [AnyTag], title: String) {
self.tags = tags
self.title = title
}
}
let tags = [
AnyTag(AuthorTag(value: "Author Tag Value", foo: 56.7, somethingElse: 67.0)),
AnyTag(GenreTag(value:"Genre Tag Value", baz: "hello world", somethingElse: true))
]
let article = Article(tags: tags, title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
print(String(decoding: jsonData, as: UTF8.self))
// {
// "title" : "Article Title",
// "tags" : [
// {
// "type" : "author",
// "base" : {
// "value" : "Author Tag Value",
// "foo" : 56.700000762939453,
// "somethingElse" : 67
// }
// },
// {
// "type" : "genre",
// "base" : {
// "value" : "Genre Tag Value",
// "baz" : "hello world",
// "somethingElse" : true
// }
// }
// ]
// }
//
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AnyTag(base: AuthorTag(value: "Author Tag Value", foo: 56.7000008, somethingElse: 67.0)),
// AnyTag(base: GenreTag(value: "Genre Tag Value", baz: "hello world", somethingElse: true))
// ], title: "Article Title")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment