Flexible JSON traversal for rapid prototyping
RBBJSON
RBBJSON enables flexible JSON traversal at runtime and JSONPath-like querying for rapid prototyping.
Use JSONDecoder
to create an RBBJSON
struct, then traverse it using dynamic member lookup:
let json = try JSONDecoder().decode(RBBJSON.self, from: data)
json.firstName // RBBJSON.string("John")
json.lastName // RBBJSON.string("Appleseed")
json.age // RBBJSON.number(26)
json.invalidKey // RBBJSON.null
json.phoneNumbers[0] // RBBJSON.string("+14086065775")
If you want to access a value that coincides with a Swift-defined property, use a String
subscript instead:
json.office.map // Error: Maps to Sequence.map
json.office["map"] // RBBJSON.string("https://maps.apple.com/?q=IL1")
To unbox a JSON value, use one of the failable initializers:
String(json.firstName) // "John"
String(json.lastName) // "Appleseed"
String(json.age) // nil
Int(json.age) // 26
Double(json.age) // 26.0
You can also make use of a JSONPath-inspired Query syntax to find nested data inside a JSON structure.
For example, given:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
JSONPath | RBBJSON | Result |
---|---|---|
$.store.book[*].author |
json.store.book[any: .child].author |
The authors of all books in the store. |
$..author |
json[any: .descendantOrSelf].author |
All authors. |
$.store.* |
json.store[any: .child] |
All things in the store, a list of books an a red bycicle. |
$.store..price |
json.store[any: .descendantOrSelf].price |
All prices in the store. |
$..book[2] |
json[any: .descendantOrSelf].book[2] |
The second book. |
$..book[-2] |
json[any: .descendantOrSelf].book[-2] |
The second-to-last book. |
$..book[0,1] , $..book[:2] |
json[any: .descendantOrSelf].book[0, 1]) , json[any: .descendantOrSelf].book[0...1]) , json[any: .descendantOrSelf].book[0..<2]) |
The first two books. |
$..book[?(@.isbn)] |
json[any: .descendantOrSelf].book[has: \.isbn] |
All books with an ISBN number. |
$..book[?(@.price<10)] |
json.store.book[matches: { $0.price <= 10 }] |
All books cheaper than 10 . |
$.store["book", "bicycle"]..["price", "author"] |
json.store["book", "bicycle"][any: .descendantOrSelf]["price", "author"] |
The author (where available) and price of every book or bicycle. |
Once you query a JSON value using one of the higher order selectors, the resulting type of the expression will be a lazy RBBJSONQuery
:
json.store.book[0]["title"] // RBBJSON.string("Sayings of the Century")
json.store.book[0, 1]["title"] // some RBBJSONQuery
Because RBBJSONQuery
conforms to Sequence
, you can initialize an Array
with it to obtain the results or use e.g. compactMap
:
String(json.store.book[0].title) // "Sayings of the Century"
json.store.book[0, 1].title.compactMap(String.init) // ["Sayings of the Century", "Sword of Honour"]
String(json.store.book[0]["invalid Property"]) // nil
json.store.book[0, 1]["invalid Property"].compactMap(String.init) // []