Created
February 11, 2020 23:11
-
-
Save ammojamo/f6cbb98db73fa4e6d1b19fb04bbab137 to your computer and use it in GitHub Desktop.
Messing around with different ways to describe JSON types in Swift
This file contains hidden or 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-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