|
| 1 | +--- |
| 2 | +keywords: "jsonpath, querying, loro, crdt, advanced" |
| 3 | +description: "Learn how to query Loro documents using RFC 9535 JSONPath support." |
| 4 | +--- |
| 5 | + |
| 6 | +# JSONPath Queries |
| 7 | + |
| 8 | +[JSONPath](https://www.rfc-editor.org/rfc/rfc9535.html) queries integrate |
| 9 | +directly with Loro documents so you can traverse complex data without writing |
| 10 | +ad hoc tree walkers. This guide outlines the query surface, highlights |
| 11 | +supported syntax, and walks through examples that you can adapt to your own |
| 12 | +application. |
| 13 | + |
| 14 | +> JSONPath functions such as `count()`, `length()`, `value()`, `match()`, and |
| 15 | +> `search()` are parsed but not evaluated at runtime. Each currently returns |
| 16 | +> `JsonPathError::EvaluationError::Unimplemented`. |
| 17 | +
|
| 18 | +## Preparing Sample Data |
| 19 | + |
| 20 | +The snippets below use the WASM bindings, but the same structure works with the |
| 21 | +Rust API. We create a bookstore dataset inside a `LoroDoc` so we can run queries |
| 22 | +against it. |
| 23 | + |
| 24 | +> ⚠️ **Illustration only**: This approach stuffs plain JavaScript objects into a |
| 25 | +> document for convenience. In production code prefer composing `LoroMap` and |
| 26 | +> `LoroList` instances explicitly so you can control how data enters the CRDT and |
| 27 | +> benefit from fine-grained updates. |
| 28 | +
|
| 29 | +```ts twoslash |
| 30 | +import { LoroDoc } from "loro-crdt"; |
| 31 | + |
| 32 | +const doc = new LoroDoc(); |
| 33 | +const testData = { |
| 34 | + books: [ |
| 35 | + { title: "1984", author: "George Orwell", price: 10, available: true, isbn: "978-0451524935" }, |
| 36 | + { title: "Animal Farm", author: "George Orwell", price: 8, available: true }, |
| 37 | + { title: "Brave New World", author: "Aldous Huxley", price: 12, available: false }, |
| 38 | + { title: "Fahrenheit 451", author: "Ray Bradbury", price: 9, available: true, isbn: "978-1451673318" }, |
| 39 | + { title: "The Great Gatsby", author: "F. Scott Fitzgerald", price: null, available: true }, |
| 40 | + { title: "To Kill a Mockingbird", author: "Harper Lee", price: 11, available: true }, |
| 41 | + { title: "The Catcher in the Rye", author: "J.D. Salinger", price: 10, available: false }, |
| 42 | + { title: "Lord of the Flies", author: "William Golding", price: 9, available: true }, |
| 43 | + { title: "Pride and Prejudice", author: "Jane Austen", price: 7, available: true }, |
| 44 | + { title: "The Hobbit", author: "J.R.R. Tolkien", price: 14, available: true } |
| 45 | + ], |
| 46 | + featured_author: "George Orwell", |
| 47 | + min_price: 10, |
| 48 | + featured_authors: ["George Orwell", "Jane Austen"], |
| 49 | +}; |
| 50 | + |
| 51 | +const store = doc.getMap("store"); |
| 52 | +Object.entries(testData).forEach(([key, value]) => store.set(key, value)); |
| 53 | +doc.commit(); |
| 54 | +``` |
| 55 | + |
| 56 | +## Executing JSONPath Queries |
| 57 | + |
| 58 | +Run a query with the `JSONPath` method on WASM or `jsonpath` on Rust. The API |
| 59 | +returns a list of values for any matches. Container results currently surface as |
| 60 | +handlers internally, but that interface is still evolving and treated as |
| 61 | +opaque in this guide. |
| 62 | + |
| 63 | +```ts twoslash |
| 64 | +import { LoroDoc } from "loro-crdt"; |
| 65 | +// ---cut--- |
| 66 | +const doc = new LoroDoc(); |
| 67 | +// setup omitted for brevity |
| 68 | +const results = doc.JSONPath("$.store.books[*].title"); |
| 69 | +console.log(results); |
| 70 | +// => ["1984", "Animal Farm", ...] |
| 71 | +``` |
| 72 | + |
| 73 | +```rust |
| 74 | +use loro::prelude::*; |
| 75 | + |
| 76 | +let doc = LoroDoc::new(); |
| 77 | +// setup omitted for brevity |
| 78 | +let titles = doc.jsonpath("$.store.books[*].title")?; |
| 79 | +for title in titles { |
| 80 | + if let ValueOrHandler::Value(value) = title { |
| 81 | + println!("{value}"); |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +### Supported Selectors and Filters |
| 87 | + |
| 88 | +RFC 9535 syntax works end-to-end, including: |
| 89 | + |
| 90 | +- **Selectors** – names, indices (positive/negative), slices, unions, wildcards, |
| 91 | + and recursive descent (`..`). |
| 92 | +- **Filters** – logical operators (`&&`, `||`, `!`), comparisons, membership via |
| 93 | + `in`, substring checks with `contains`, property existence, and nested |
| 94 | + subqueries through `$` (root) or `@` (current node). |
| 95 | +- **Functions** – `count()`, `length()`, and `value()` are parsed, while `match()` |
| 96 | + and `search()` are reserved for expansion. All functions currently return an |
| 97 | + unimplemented evaluation error at runtime. |
| 98 | + |
| 99 | +## Cookbook Examples |
| 100 | + |
| 101 | +Once the document is populated you can combine selectors and filters to extract |
| 102 | +precisely what you need. |
| 103 | + |
| 104 | +| Query | Description | Result | |
| 105 | +| ----- | ----------- | ------ | |
| 106 | +| `$.store.books[*].title` | All book titles. | `["1984", "Animal Farm", "Brave New World", "Fahrenheit 451", "The Great Gatsby", "To Kill a Mockingbird", "The Catcher in the Rye", "Lord of the Flies", "Pride and Prejudice", "The Hobbit"]` | |
| 107 | +| `$.store.books[?(@.available)].title` | Titles of books in stock. | `["1984", "Animal Farm", "The Great Gatsby", "To Kill a Mockingbird", "Lord of the Flies", "Pride and Prejudice", "The Hobbit"]` | |
| 108 | +| `$.store.books[?(@.author in $.store.featured_authors)].title` | Books by featured authors. | `["1984", "Animal Farm", "Pride and Prejudice"]` | |
| 109 | +| `$.store.books[?(@.price > 12)].title` | Books priced above 12. | `["The Hobbit"]` | |
| 110 | +| `$..price` | All price fields via recursive descent. | `[10, 8, 12, 9, null, 11, 10, 9, 7, 14]` | |
| 111 | +| `$.store.books[0:3].title` | Slice syntax for the first three titles. | `["1984", "Animal Farm", "Brave New World"]` | |
| 112 | +| `$.store.books[0,2,-1].title` | Union of specific indices. | `["1984", "Brave New World", "The Hobbit"]` | |
| 113 | +| `count($.store.books[?(@.available)])` | Planned helper to count available books *(currently returns an unimplemented error)*. | `JsonPathError::EvaluationError::Unimplemented` | |
| 114 | +| `length($.store.featured_authors)` | Planned array length helper *(currently returns an unimplemented error)*. | `JsonPathError::EvaluationError::Unimplemented` | |
| 115 | +| `$.store.books[?(@.isbn && @.price >= $.store.min_price)].title` | Filter by field existence and comparison. | `["1984"]` | |
| 116 | +| `$.store.books[?(!@.available)].title` | Negated availability filter. | `["Brave New World", "The Catcher in the Rye"]` | |
| 117 | +| `$.store.books[?(@.title contains "The")].author` | Authors with "The" in the title. | `["F. Scott Fitzgerald", "J.R.R. Tolkien"]` | |
| 118 | + |
| 119 | +> JSONPath always returns values in document order. When a filter references |
| 120 | +> another query (such as `$.store.featured_authors`), the subquery is evaluated |
| 121 | +> for each candidate element. |
0 commit comments