-
-
Save hamishknight/5ffe87a43590a1f1fae8e341cf0da418 to your computer and use it in GitHub Desktop.
This file contains 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 | |
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