Overview
When syncing data from your backend source database to PowerSync, JSON columns (whether from MongoDB documents, Postgres JSONB columns, or other JSON data types) are stored asTEXT in SQLite. See the type mapping guide for more details. This guide shows you how to effectively query and filter JSON data using SQLite’s powerful JSON functions on the client.
Understanding JSON Storage in PowerSync
Your backend source database might store structured data as JSON in various ways:- MongoDB: Nested documents and arrays
- Postgres: JSONB, JSON, array, or custom types
- MySQL: JSON columns
- SQL Server: JSON columns
TEXT columns. On the client side, you can query this data using SQLite’s built-in JSON functions without needing to parse it yourself. Learn more about how PowerSync handles JSON, arrays, and custom types.
Example Data Structure
Let’s use a task management system where tasks have nested metadata:assignees, tags, and metadata columns are stored as JSON strings. For details on how different backend types map to SQLite, see database types and mapping.
JSON Extraction Basics
Standard json_extract() Function
Extract values from JSON using path expressions:
$- root element.- object member access[index]- array element access
Shorthand: The -> and ->> Operators
SQLite provides convenient shorthand operators for JSON extraction:->returns JSON (preserves type information, quotes strings)->>extracts the value unquoted (strings as TEXT, numbers/booleans as their native types)
Nested Path Access
Access deeply nested values:Querying Arrays with json_each()
Flattening Simple Arrays
For thetags array, use json_each() to create one row per element:
json_each(t.tags)creates a virtual table with one row per tagtag.valuecontains each individual tag string- You can filter, join, or aggregate these expanded rows
Querying Nested Objects in Arrays
For complex objects likeassignees:
- Each
assignee.valueis a JSON object representing one assignee - Use
->>to extract text values for comparison - Use
->when you need numeric values for calculations
Real-World Query Examples
Example 1: Finding Tasks by Assignee
Use case: Show all tasks assigned to a specific user.Example 2: Calculating Total Hours by Role
Use case: Aggregate hours across all tasks grouped by role.Example 3: Tasks with Specific Tags
Use case: Find tasks tagged with multiple specific tags.Example 4: Filtering by Array Contents
Use case: Find tasks that depend on a specific task ID.Example 5: Checking for Array Membership
Use case: Check if a task has any dependencies.Working with Comma or Delimiter-Separated Values
Sometimes JSON strings contain delimiter-separated values (like"NYC;LAX;MIA"). Here’s how to query them efficiently:
- Wraps the value:
";user_001;user_002;user_003;" - Searches for
;user_001;ensuring exact delimiter-bounded match - Prevents false matches (won’t match “user_0011” when searching for “user_001”)
LIKE for delimited strings:
Advanced Techniques
Using CTEs for Cleaner Queries
Common Table Expressions make complex JSON queries more readable:Combining Multiple JSON Arrays
Query across multiple nested arrays:Checking for Key Existence
Verify if a JSON key exists:Performance Optimization
Important Performance Considerations- Index JSON columns for better performance: If you frequently query JSON fields, add indexes to the JSON string columns in your
AppSchema:
-
Minimize
json_each()usage: Eachjson_each()call expands rows. For a table with 10,000 tasks averaging 5 assignees each, you’re processing 50,000 rows. - Use EXISTS for membership checks: More efficient than joining:
- Cache extracted values in CTEs: Extract once, use multiple times:
Useful JSON Functions
Beyond extraction, SQLite offers many JSON utilities:Common Gotchas
Watch out for these common issues:- NULL vs missing keys:
json_extract()returnsNULLfor non-existent paths. Always check for NULL:
- Type mismatches:
- Array index bounds: Out-of-bounds array access returns NULL, not an error:
- Quotes in JSON strings: Use
->>to get unquoted text, not->:
- Performance on large arrays:
json_each()on arrays with thousands of elements can be slow. Consider data restructuring for such cases.
Summary
Querying JSON data in SQLite effectively requires:- Understanding that JSON is stored as strings but queryable with built-in functions
- Using
json_extract()or the shorthand->and->>operators - Leveraging
json_each()to flatten arrays for filtering and aggregation - Being mindful of type conversions and NULL handling
- Optimizing queries by filtering early and considering denormalization for critical paths