Skip to content

Instantly share code, notes, and snippets.

@ammojamo
Created February 11, 2020 23:11
Show Gist options
  • Save ammojamo/f6cbb98db73fa4e6d1b19fb04bbab137 to your computer and use it in GitHub Desktop.
Save ammojamo/f6cbb98db73fa4e6d1b19fb04bbab137 to your computer and use it in GitHub Desktop.
Messing around with different ways to describe JSON types in Swift
import Foundation
// Protocol-based approach
protocol JSONValue {}
extension Int: JSONValue {}
extension String: JSONValue {}
extension Bool: JSONValue {}
extension Double: JSONValue {}
extension NSNull: JSONValue {}
typealias JSONObject = [String: JSONValue]
typealias JSONArray = [JSONValue]
extension JSONObject: JSONValue {
subscript(object key: String) -> JSONObject? {
get {
return self[key] as? JSONObject
}
mutating set {
self[key] = newValue
}
}
subscript(array key: String) -> JSONArray? {
get {
return self[key] as? JSONArray
}
mutating set {
self[key] = newValue
}
}
}
extension JSONArray: JSONValue {}
//
//extension Array: JSONValue where Element == JSONValue {
//// subscript(object index: Index) -> [String:JSONValue]? {
//// get {
//// return self[index] as? [String:JSONValue]
//// }
//// mutating set {
//// if let newValue = newValue { self[index] = newValue }
//// }
//// }
//// subscript(array index: Index) -> [JSONValue]? {
//// get {
//// return self[index] as? [JSONValue]
//// }
//// mutating set {
//// if let newValue = newValue as? JSONValue { self[index] = newValue }
//// }
//// }
//}
//extension Dictionary: JSONValue where Key == String, Value == JSONValue {
//// subscript(object key: String) -> [String:JSONValue]? {
//// get {
//// return self[key] as? [String:JSONValue]
//// }
//// mutating set {
//// self[key] = newValue
//// }
//// }
//// subscript(array key: String) -> [JSONValue]? {
//// get {
//// return self[key] as? [JSONValue]
//// }
//// mutating set {
//// self[key] = newValue
//// }
//// }
//}
let a: [String:Any] = ["foo": "bar", "baz": Date(), "donkey": [1, 2, "#", Date()]]
let b: [String:Any] = ["foo": 4]
var x: JSONObject = ["foo": 4, "bar": 4, "baz": [1, 2]]
var y: JSONValue = ["foo": 5, "bar": 4]
x[object: "bar"] = x[object: "bar"] ?? [:]
x[object: "bar"]?[array: "foo"] = [1,2]
x
enum JSONError: Error {
case invalidJSONValue(Any)
}
func wrap2(value: Any) throws -> JSONValue {
switch value {
case let value as JSONValue:
return value
case let value as [String:Any]:
return try value.mapValues { try wrap2(value: $0) }
case let value as [Any]:
return try value.map { try wrap2(value: $0) }
default:
throw JSONError.invalidJSONValue(value)
}
}
var z = try wrap2(value: a)
func wrap(value: Any) -> JSONValue? {
switch value {
case let value as JSONValue:
return value
case let value as [String:Any]:
return wrap(object: value)
case let value as [Any]:
return wrap(array: value)
default:
return nil
}
}
func wrap(object: [String: Any]) -> [String:JSONValue] {
return object.compactMapValues { wrap(value: $0) }
}
func wrap(array: [Any]) -> [JSONValue] {
return array.compactMap { wrap(value: $0) }
}
var b = wrap(object: a)
func ==(_ lhs: JSONObject, _ rhs: JSONObject) -> Bool {
return lhs.keys == rhs.keys && lhs.keys.allSatisfy { lhs[$0] == rhs[$0] }
}
func ==(_ lhs: JSONArray, _ rhs: JSONArray) -> Bool {
return lhs.elementsEqual(rhs) { $0 == $1 }
}
func ==(_ lhs: JSONValue, _ rhs: JSONValue) -> Bool {
switch (lhs, rhs) {
case let (lhs, rhs) as (Int, Int): return lhs == rhs
case let (lhs, rhs) as (String, String): return lhs == rhs
case let (lhs, rhs) as (Bool, Bool): return lhs == rhs
case let (lhs, rhs) as (Double, Double): return lhs == rhs
case let (lhs, rhs) as (JSONArray, JSONArray): return lhs == rhs
case let (lhs, rhs) as (JSONObject, JSONObject): return lhs == rhs
default: return false
}
}
func ==(_ lhs: JSONValue?, _ rhs: JSONValue?) -> Bool {
switch (lhs, rhs) {
case let (.some(lhs), .some(rhs)): lhs == rhs
case (.none, .none): return true
default: return false
}
}
x == y
wrap(a) == wrap(a)
// Enum-based approach:
enum JSON: Equatable {
case object([String:JSON])
case array([JSON])
case string(String)
case bool(Bool)
case int(Int)
case double(Double)
case null
static func wrap(_ dict: [String:Any]) -> [String:JSON] {
return dict.compactMapValues(JSON.wrap)
}
static func wrap(_ array: [Any]) -> [JSON] {
return array.compactMap(JSON.wrap)
}
static func wrap(_ value: Any) -> JSON? {
switch value {
case let value as Bool:
return .bool(value)
case let value as [String:Any]:
return .object(wrap(value))
case let value as [Any]:
return .array(wrap(value))
case let value as String:
return .string(value)
case let value as Int:
return .int(value)
case let value as Double:
return .double(value)
case let value as Float:
return .double(Double(value))
case let value as JSON: // Not sure if this is a good idea but seems like it could be handy
return value
case is NSNull:
return .null
default:
return nil
}
}
func unwrap() -> Any {
switch self {
case let .object(x): return x.mapValues { $0.unwrap() }
case let .array(x): return x.map { $0.unwrap() }
case let .string(x): return x
case let .bool(x): return x
case let .int(x): return x
case let .double(x): return x
case .null: return NSNull()
}
}
}
extension Array where Element == JSON {
func unwrap() -> [Any] {
map { $0.unwrap }
}
subscript(string index: Index) -> String? {
get {
if case let .string(x) = self[index] {
return x
} else {
return nil
}
}
mutating set {
if let x = newValue {
self[index] = .string(x)
}
}
}
}
extension Dictionary where Key == String, Value == JSON {
func unwrap() -> [String:Any] {
mapValues { $0.unwrap() }
}
subscript(object key: String) -> [String:JSON]? {
get {
if case let .object(x) = self[key] {
return x
} else {
return nil
}
}
mutating set {
if let x = newValue {
self[key] = .object(x)
} else {
self[key] = nil
}
}
}
subscript(string key: String) -> String? {
get {
if case let .string(x) = self[key] {
return x
} else {
return nil
}
}
mutating set {
if let x = newValue {
self[key] = .string(x)
} else {
self[key] = nil
}
}
}
subscript(int key: String) -> Int? {
get {
if case let .int(x) = self[key] {
return x
} else {
return nil
}
}
mutating set {
if let x = newValue {
self[key] = .int(x)
} else {
self[key] = nil
}
}
}
}
// Testing:
//
//let a: [String:Any] = [ "foo": ["bar": 1], "baz": 2 ]
//
//var j = JSON.wrap(a)
//
//j[object: "foo"]?[string: "baz"] = "hi"
//
//j[object: "foo"]?[string: "baz"]
//
//var k = JSON.wrap([:])
//k[object: "foo"] = JSON.wrap(["baz": "hi"])
//k[int: "baz"] = 2
//k[object: "foo"]?[int: "bar"] = 1
//
//k[object: "foo"]?[int: "bar"]
//
//j == k
//
//
//try String(data: JSONSerialization.data(withJSONObject: j.unwrap()), encoding: .utf8)
//try String(data: JSONSerialization.data(withJSONObject: k.unwrap()), encoding: .utf8)
//
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment