This is an experiment with constructing and composing functions for querying JSON values. A full write-up is available on my blog.
enum JSON { | |
case array([JSON]) | |
case object([String: JSON]) | |
case number(Int) | |
case string(String) | |
case bool(Bool) | |
} | |
extension JSON: ExpressibleByIntegerLiteral { | |
init(integerLiteral: Int) { | |
self = .number(integerLiteral) | |
} | |
} | |
extension JSON: ExpressibleByBooleanLiteral { | |
init(booleanLiteral: Bool) { | |
self = .bool(booleanLiteral) | |
} | |
} | |
extension JSON: ExpressibleByStringLiteral { | |
typealias StringLiteralType = String | |
typealias ExtendedGraphemeClusterLiteralType = String | |
typealias UnicodeScalarLiteralType = String | |
init(stringLiteral: String) { | |
self = .string(stringLiteral) | |
} | |
init(extendedGraphemeClusterLiteral: String) { | |
self.init(stringLiteral: extendedGraphemeClusterLiteral) | |
} | |
init(unicodeScalarLiteral: String) { | |
self.init(stringLiteral: unicodeScalarLiteral) | |
} | |
} | |
extension JSON: ExpressibleByDictionaryLiteral { | |
typealias Key = String | |
typealias Value = JSON | |
init(dictionaryLiteral: (Key, Value)...) { | |
var dict: [Key: Value] = [:] | |
for (key, value) in dictionaryLiteral { | |
dict[key] = value | |
} | |
self = .object(dict) | |
} | |
} | |
extension JSON: ExpressibleByArrayLiteral { | |
init(arrayLiteral: Value...) { | |
self = .array(arrayLiteral) | |
} | |
} | |
func array(_ input: JSON) -> JSON? { | |
switch input { | |
case .array: return input | |
default: return nil | |
} | |
} | |
func object(_ input: JSON) -> JSON? { | |
switch input { | |
case .object: return input | |
default: return nil | |
} | |
} | |
func key(_ key: String) -> (JSON) -> JSON? { | |
return { input in | |
switch object(input) { | |
case let .some(.object(dict)): | |
return dict[key] | |
default: | |
return nil | |
} | |
} | |
} | |
func index(_ index: Int) -> (JSON) -> JSON? { | |
return { input in | |
switch array(input) { | |
case let .some(.array(array)): | |
if index > array.count - 1 { | |
return nil | |
} | |
else { | |
return array[index] | |
} | |
default: | |
return nil | |
} | |
} | |
} | |
func number(_ input: JSON) -> JSON? { | |
switch input { | |
case .number: return input | |
default: return nil | |
} | |
} | |
func bool(_ input: JSON) -> JSON? { | |
switch input { | |
case .bool: return input | |
default: return nil | |
} | |
} | |
func string(_ input: JSON) -> JSON? { | |
switch input { | |
case .string: return input | |
default: return nil | |
} | |
} | |
typealias JSONQuery = (JSON) -> JSON? | |
infix operator >>: AdditionPrecedence | |
func >> (lhs: @escaping JSONQuery, rhs: @escaping JSONQuery) -> JSONQuery { | |
return { input in | |
guard let result = lhs(input) else { return nil } | |
return rhs(result) | |
} | |
} | |
let json: JSON = ["herp": [1, 2, 4]] | |
let query = object >> key("herp") >> array >> index(2) >> number | |
print(query(json) ?? "booo") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment