Skip to content

Instantly share code, notes, and snippets.

@harrytwright
Created March 15, 2018 14:18
Show Gist options
  • Select an option

  • Save harrytwright/6cadb8e19c12525a7bf2b844baaeaa8a to your computer and use it in GitHub Desktop.

Select an option

Save harrytwright/6cadb8e19c12525a7bf2b844baaeaa8a to your computer and use it in GitHub Desktop.

Revisions

  1. harrytwright created this gist Mar 15, 2018.
    151 changes: 151 additions & 0 deletions AnyCodable.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,151 @@
    import Foundation

    internal protocol _AnyCodableBox {

    var _base: Any { get }

    var objectIdentifier: ObjectIdentifier { get }

    }

    internal struct _ConcreteCodableBox<Base : Codable> : _AnyCodableBox {

    internal var _baseCodable: Base

    var objectIdentifier: ObjectIdentifier {
    return ObjectIdentifier(type(of: _baseCodable))
    }

    init(_ base: Base) {
    self._baseCodable = base
    }

    var _base: Any {
    return _baseCodable
    }
    }

    public struct AnyCodable: Encodable, Decodable {

    internal var _box: _AnyCodableBox

    public var base: Any {
    return _box._base
    }

    public var objectIdentifier: ObjectIdentifier {
    return _box.objectIdentifier
    }

    public init<Base: Codable>(_ base: Base) {
    self._box = _ConcreteCodableBox<Base>(base)
    }

    public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    if let intValue = try? container.decode(Int.self) {
    self._box = _ConcreteCodableBox<Int>(intValue)
    } else if let doubleValue = try? container.decode(Double.self) {
    self._box = _ConcreteCodableBox<Double>(doubleValue)
    } else if let floatValue = try? container.decode(Float.self) {
    self._box = _ConcreteCodableBox<Float>(floatValue)
    } else if let stringValue = try? container.decode(String.self) {
    self._box = _ConcreteCodableBox<String>(stringValue)
    } else if let dictionaryValue = try? container.decode([String:AnyCodable].self) {
    self._box = _ConcreteCodableBox<[String:AnyCodable]>(dictionaryValue)
    } else if let arrayValue = try? container.decode([AnyCodable].self) {
    self._box = _ConcreteCodableBox<[AnyCodable]>(arrayValue)
    } else {
    throw DecodingError.typeMismatch(
    type(of: self),
    .init(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON type")
    )
    }
    }

    public func encode(to encoder: Encoder) throws {
    // Safe to use `as!` becasue the `_base` is just `_baseCodable`
    try (self._box._base as! Codable).encode(to: encoder)
    }

    }

    /**
    Having AnyCodable conforming to the Expressible methods means users can
    create a dictionary or array that looks like a normal one
    */
    extension AnyCodable:
    ExpressibleByStringLiteral,
    ExpressibleByIntegerLiteral,
    ExpressibleByBooleanLiteral,
    ExpressibleByFloatLiteral,
    ExpressibleByArrayLiteral
    {

    public init(arrayLiteral elements: AnyCodable...) {
    self.init(Array<AnyCodable>(elements))
    }

    public init(stringLiteral value: String) {
    self.init(value)
    }

    public init(integerLiteral value: Int) {
    self.init(value)
    }

    public init(booleanLiteral value: Bool) {
    self.init(value)
    }

    public init(floatLiteral value: Float) {
    self.init(value)
    }

    }

    extension AnyCodable: Hashable {

    public static func ==(lhs: AnyCodable, rhs: AnyCodable) -> Bool {
    return lhs.hashValue == rhs.hashValue
    }

    public var hashValue: Int {
    if let hash = try? JSONEncoder().encode(self).hashValue {
    return hash
    } else if self.base is Int {
    return (self.base as! Int).hashValue
    } else if self.base is Double {
    return (self.base as! Double).hashValue
    } else if self.base is Float {
    return (self.base as! Float).hashValue
    } else if self.base is Bool {
    return (self.base as! Bool).hashValue
    } else if self.base is String {
    return (self.base as! String).hashValue
    } else {
    fatalError("Unsuported type")
    }
    }

    }

    extension AnyCodable: CustomStringConvertible {

    public var description: String {
    return "AnyCodable(\(self.base))"
    }
    }

    extension AnyCodable: CustomReflectable {

    public var customMirror: Mirror {
    return Mirror(
    self,
    children: [
    "value": base
    ]
    )
    }

    }