Skip to content

Conversation

@bean1352
Copy link
Contributor

Added comprehensive guide for querying JSON data in the client SQLite database

  • Explains JSON storage as strings in SQLite when synced from backend (MongoDB, PostgreSQL, MySQL)
  • Documents -> vs ->> operators with clear examples showing quoted vs unquoted output
  • Covers json_extract() and json_each() for flattening and querying nested arrays/objects
  • Includes real-world examples: filtering, aggregating etc
  • Shows delimiter-separated value queries using instr() pattern
  • Performance optimization tips: indexing JSON columns in AppSchema, filtering before expanding, using EXISTS
  • Documents common gotchas: NULL handling, type casting
  • Provides reference for useful JSON functions (json_array_length, json_valid, json_type, etc.)

@bean1352 bean1352 requested review from benitav and rkistner November 26, 2025 10:43
@benitav
Copy link
Collaborator

benitav commented Nov 26, 2025

this is great! 🔥 One suggestion I have is to link to this reference from other places in the docs where this use case would come up, e.g. two that come to mind are https://docs.powersync.com/usage/use-case-examples/custom-types-arrays-and-json (maybe even consolidate these 2 pages as there seems to be a lot of overlap), and where we mention JSON on https://docs.powersync.com/usage/sync-rules/types

…ate text column reference and add links for further information on JSON handling and database type mapping.
@michaelbarnes michaelbarnes self-requested a review December 1, 2025 15:28
Copy link
Contributor

@michaelbarnes michaelbarnes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this is a great addition. I've left a few comments around wording and a few links back to SQLite docs for easy reference.

… links to relevant SQLite functions for better user guidance.
Copy link
Contributor

@michaelbarnes michaelbarnes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small changes to be consistent.

…ON functions, corrected example references to 'tasks', and added a link to the SQLite JSON functions for enhanced user guidance.

Your backend database might store structured data in various ways:
- **MongoDB**: Documents with nested documents and arrays
- **PostgreSQL**: JSONB or JSON column types
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some additional Postgres types are also represented as JSON, notably arrays and custom types.

Comment on lines +82 to +83
- `->` returns JSON (preserves type information, quotes strings)
- `->>` returns TEXT (unquoted, for final values)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in the examples below, but this is not TEXT for numbers / booleans.

assignee.value -> '$.hours_allocated' AS hours
FROM tasks t,
json_each(t.assignees) AS assignee
WHERE assignee.value ->> '$.role' = 'developer';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth making this WHERE (assignee.value ->> '$.role') = 'developer' to make the order of operations clear (same in other examples below).

```

<Info>
**Note the CAST**: JSON numbers are extracted as text. When doing numeric comparisons or sorting, cast to the appropriate type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use ->> here, instead of the CAST?

```sql
SELECT
assignee.value ->> '$.role' AS role,
SUM(CAST(assignee.value -> '$.hours_allocated' AS INTEGER)) AS total_hours,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, use ->> here instead of the CAST

t.title,
assignee.value ->> '$.user_id' AS user_id,
assignee.value ->> '$.role' AS role,
CAST(assignee.value -> '$.hours_allocated' AS INTEGER) AS hours
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one that should be ->>.

Comment on lines +356 to +369
-- ✅ GOOD - Filter by title first, then expand JSON
SELECT t.*, assignee.value
FROM tasks t,
json_each(t.assignees) AS assignee
WHERE t.title LIKE '%homepage%'
AND assignee.value ->> '$.role' = 'developer';

-- ❌ LESS EFFICIENT - Expands all rows first
SELECT t.*, assignee.value
FROM tasks t,
json_each(t.assignees) AS assignee
WHERE assignee.value ->> '$.role' = 'developer'
AND t.title LIKE '%homepage%';
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure these two are different? the SQLite query optimizer is pretty good at re-ordering filters to efficiently execute the query.

-- vs joining which creates all row combinations
```

5. **Cache extracted values in CTEs**: Extract once, use multiple times:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is being used multiple times in this example? It doesn't look like the CTE would make any difference in performance here.

Comment on lines +431 to +438
2. **Type mismatches**: JSON numbers are returned as text with `->`. Always cast for numeric operations:
```sql
-- ❌ String comparison (wrong!)
WHERE metadata -> '$.priority' > 5

-- ✅ Numeric comparison (correct!)
WHERE CAST(metadata -> '$.priority' AS INTEGER) > 5
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - should just use ->>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants