Skip to content

Instantly share code, notes, and snippets.

@ollieatkinson
Last active April 8, 2024 21:41
Show Gist options
  • Save ollieatkinson/e05e0eb358b22b2038f50101c1c873e6 to your computer and use it in GitHub Desktop.
Save ollieatkinson/e05e0eb358b22b2038f50101c1c873e6 to your computer and use it in GitHub Desktop.
JSON Equatable in Swift
public let null = NSNull() as AnyHashable
public enum JSONObject {
case array([Any])
case dictionary([String: Any])
case fragment(Any)
}
extension JSONObject {
@inlinable public static func with(_ data: Data, options: JSONSerialization.ReadingOptions = []) throws -> JSONObject {
try self.init(JSONSerialization.jsonObject(with: data, options: options))
}
}
extension JSONObject {
@inlinable public func decode<T>(as type: T.Type = T.self, using decoder: JSONDecoder = .init()) throws -> T where T: Decodable {
try decoder.decode(type, from: JSONSerialization.data(withJSONObject: any, options: .fragmentsAllowed))
}
@inlinable public func encode(options: JSONSerialization.WritingOptions = [.fragmentsAllowed]) throws -> Data {
try JSONSerialization.data(withJSONObject: any, options: options)
}
}
extension JSONObject {
public init(_ any: Any) {
switch any {
case let array as [Any]: self = .array(array)
case let dictionary as [String: Any]: self = .dictionary(dictionary)
case let fragment: self = .fragment(fragment)
}
}
public var any: Any {
switch self {
case let .array(o): return o
case let .dictionary(o): return o
case let .fragment(o): return o
}
}
}
extension JSONObject {
@inlinable public var dictionary: [String: Any]? {
guard case .dictionary(let dictionary) = self else { return nil }
return dictionary
}
@inlinable public var array: [Any]? {
guard case .array(let array) = self else { return nil }
return array
}
@inlinable public var fragment: Any? {
guard case .fragment(let fragment) = self else { return nil }
return fragment
}
}
extension JSONObject: CustomStringConvertible {
public var description: String { "\(any)" }
}
extension String: Error { }
extension JSONObject: CustomDebugStringConvertible {
public var debugDescription: String {
do {
guard let description = try String(data: encode(options: [.fragmentsAllowed, .prettyPrinted]), encoding: .utf8) else {
throw "Malformed UTF8"
}
return description
} catch {
return """
🚫 Could not serialise object to json!
\(error)
"""
}
}
}
extension JSONObject {
public func isEqual<T>(to other: T) -> Bool where T: Equatable {
T.isEqual(any, other)
}
}
extension JSONObject {
public func isEqual(to any: Any) -> Bool {
switch self {
case let .array(o):
return [AnyHashable].isEqual(o, any)
case let .dictionary(o):
return [String: AnyHashable].isEqual(o, any)
case let .fragment(o):
return AnyHashable.isEqual(o, any)
}
}
public func isEqual(to json: JSONObject) -> Bool {
switch (self, json) {
case let (.array(o), .array(json)):
return [AnyHashable].isEqual(o, json)
case let (.dictionary(o), .dictionary(json)):
return [String: AnyHashable].isEqual(o, json)
case let (.fragment(o), .fragment(json)):
return AnyHashable.isEqual(o, json)
default: return false
}
}
}
extension Equatable {
public static func isEqual(_ l: Any, _ r: Any) -> Bool {
guard let l = l as? Self else { return false }
return isEqual(l, r)
}
public static func isEqual(_ l: Self, _ r: Any) -> Bool {
isEqual(r, l)
}
public static func isEqual(_ l: Any, _ r: Self) -> Bool {
guard let l = l as? Self else { return false }
return l == r
}
}
extension JSONObject: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: AnyHashable...) {
self = .array(elements)
}
}
extension JSONObject: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, AnyHashable)...) {
self = .dictionary(Dictionary(uniqueKeysWithValues: elements))
}
}
extension JSONObject: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .fragment(value)
}
}
extension JSONObject: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .fragment(value)
}
}
extension JSONObject: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self = .fragment(null)
}
}
extension JSONObject: ExpressibleByIntegerLiteral {
public init(integerLiteral value: IntegerLiteralType) {
self = .fragment(value)
}
}
extension JSONObject: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .fragment(value)
}
}
@ollieatkinson
Copy link
Author

ollieatkinson commented Oct 1, 2020

let data = """
{
    "account": {
        "name": "ollieatkinson"
    }
}
""".data(using: .utf8)!

let json = try JSONObject.with(data)

json == [ "account": [ "name": "ollieatkinson" ] ] // true

json == [ "account": [ "name": "not" ] ] // false

json == "ollieatkinson" // false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment