https://github.com/robb/rbbjson
Flexible JSON traversal for rapid prototyping.
https://github.com/robb/rbbjson
data-science json jsonpath prototyping swift
Last synced: about 1 year ago
JSON representation
Flexible JSON traversal for rapid prototyping.
- Host: GitHub
- URL: https://github.com/robb/rbbjson
- Owner: robb
- License: mit
- Created: 2020-12-24T14:32:46.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2021-10-13T15:11:31.000Z (over 4 years ago)
- Last Synced: 2025-03-12T16:41:37.609Z (about 1 year ago)
- Topics: data-science, json, jsonpath, prototyping, swift
- Language: Swift
- Homepage:
- Size: 40 KB
- Stars: 162
- Watchers: 2
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# 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][dml]:
```swift
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:
```swift
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:
```swift
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:
```json
{
"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.](/Tests/RBBJSONTests/READMETests.swift#L46-L51)|
|`$..author`|`json[any: .descendantOrSelf].author`|[All authors.](/Tests/RBBJSONTests/READMETests.swift#L56-L61)|
|`$.store.*`|`json.store[any: .child]`|[All things in the store, a list of books an a red bycicle.](/Tests/RBBJSONTests/READMETests.swift#L66-L99)|
|`$.store..price`|`json.store[any: .descendantOrSelf].price`|[All prices in the store.](/Tests/RBBJSONTests/READMETests.swift#L104-L110)|
|`$..book[2]`|`json[any: .descendantOrSelf].book[2]`|[The second book.](/Tests/RBBJSONTests/READMETests.swift#L115-L123)|
|`$..book[-2]`|`json[any: .descendantOrSelf].book[-2]`|[The second-to-last book.](/Tests/RBBJSONTests/READMETests.swift#L128-L136)|
|`$..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.](/Tests/RBBJSONTests/READMETests.swift#L141-L154)|
|`$..book[?(@.isbn)]`|`json[any: .descendantOrSelf].book[has: \.isbn]`|[All books with an ISBN number.](/Tests/RBBJSONTests/READMETests.swift#L159-L174)|
|`$..book[?(@.price<10)]`|`json.store.book[matches: { $0.price <= 10 }]`|[All books cheaper than `10`.](/Tests/RBBJSONTests/READMETests.swift#L179-L193)|
|`$.store["book", "bicycle"]..["price", "author"]`|`json.store["book", "bicycle"][any: .descendantOrSelf]["price", "author"]`|[The author (where available) and price of every book or bicycle.](/Tests/RBBJSONTests/READMETests.swift#L203-L223)|
Once you query a JSON value using one of the higher order selectors, the resulting type of the expression will be a lazy `RBBJSONQuery`:
```swift
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`:
```swift
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) // []
```
---
[jsonpath]: https://goessner.net/articles/JsonPath/
[dml]: https://oleb.net/blog/2018/06/dynamic-member-lookup/