Skip to content

JSON/JSONB Filtering

Query nested fields within JSON/JSONB columns using dot notation. This is a PostgreSQL-specific feature.

Basic JSON Path Syntax

typescript
// Column: metadata jsonb
// Data: { "user": { "id": 123, "role": "admin" }, "tags": ["urgent"] }

// Simple nested field
{ where: { 'metadata.user.id': 123 } }
// SQL: "metadata" #>> '{user,id}' = '123'

// Deep nesting
{ where: { 'metadata.user.role': 'admin' } }
// SQL: "metadata" #>> '{user,role}' = 'admin'

// Array index access
{ where: { 'metadata.tags[0]': 'urgent' } }
// SQL: "metadata" #>> '{tags,0}' = 'urgent'

// Kebab-case keys
{ where: { 'metadata.user-id': 'abc123' } }
// SQL: "metadata" #>> '{user-id}' = 'abc123'

Supported Path Formats

FormatExampleSQL Path
Simple fieldmetadata.name{name}
Nested fieldmetadata.user.email{user,email}
Array indexmetadata.tags[0]{tags,0}
Nested with arraymetadata.items[2].name{items,2,name}
Kebab-casemetadata.user-id{user-id}

JSON with Operators

All standard operators work with JSON paths:

typescript
// Numeric comparison (automatic safe casting)
{ where: { 'metadata.score': { gt: 80 } } }
// SQL: CASE WHEN ("metadata" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
//      THEN ("metadata" #>> '{score}')::numeric ELSE NULL END > 80

// Range comparison
{ where: { 'metadata.priority': { gte: 1, lte: 5 } } }

// Between
{ where: { 'metadata.score': { between: [70, 90] } } }

// Pattern matching
{ where: { 'metadata.level': { ilike: '%high%' } } }
// SQL: "metadata" #>> '{level}' ILIKE '%high%'

// IN operator
{ where: { 'metadata.status': { in: ['pending', 'review'] } } }

Safe Numeric Casting

JSON fields may contain mixed types. Ignis uses safe casting to prevent database errors:

typescript
// Data in database:
// Row 1: { "score": 85 }      <- number
// Row 2: { "score": "high" }  <- string
// Row 3: { "score": null }    <- null

// Query with numeric operator
{ where: { 'metadata.score': { gt: 50 } } }

// Result:
// Row 1: 85 > 50 -> matched
// Row 2: "high" -> NULL -> not matched
// Row 3: null -> NULL -> not matched
JSON ValueNumeric Operation Result
{ "score": 85 }Compares as 85
{ "score": "high" }Treated as NULL (no match)
{ "score": null }Treated as NULL (no match)

Boolean Values

Booleans are compared as TEXT strings:

typescript
// JSON data: { "enabled": true }

{ where: { 'metadata.enabled': true } }
// SQL: "metadata" #>> '{enabled}' = 'true'

JSON Path Ordering

Order results by JSON fields:

typescript
{ filter: { order: ['metadata.priority DESC'] } }
// SQL: ORDER BY "metadata" #> '{priority}' DESC

// Multiple JSON fields
{ filter: { order: ['metadata.priority DESC', 'metadata.score ASC'] } }

Sort Order for JSONB Types:

JSONB TypeSort Order
nullFirst (lowest)
booleanfalse < true
numberNumeric order
stringLexicographic
arrayElement-wise
objectKey-value

Path Validation & Security

Path components are validated to prevent SQL injection:

typescript
// Valid paths
'metadata.fieldName'
'metadata.nested.deep.value'
'data.items[0]'
'config.user_id'
'data.meta-data'  // kebab-case allowed

// Invalid (throws error)
'metadata.field;DROP TABLE'
'data.123invalid'
'config.(SELECT * FROM users)'

Error Messages:

// Non-JSON column
Error: Column 'name' is not JSON/JSONB type | dataType: 'text'

// Invalid path
Error: Invalid JSON path component: 'field;DROP'

Performance Tips

  1. Index Your JSON Paths:
sql
CREATE INDEX idx_metadata_priority ON "Product" (("metadata" ->> 'priority'));
CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
  1. Use Appropriate Types in JSON:
json
// Good
{ "priority": 3, "enabled": true }

// Bad
{ "priority": "3", "enabled": "true" }
  1. Keep Paths Shallow:
typescript
// Easier to work with
'metadata.priority'

// Harder to optimize
'data.level1.level2.level3.level4.value'

Null-Safe JSON Paths

typescript
// If JSON field doesn't exist, #>> returns NULL
// This is safe - no errors, just no matches
{ where: { 'metadata.nonexistent.field': 'value' } }
// SQL: "metadata" #>> '{nonexistent,field}' = 'value'
## See Also

- [Nested JSON Updates](../repositories/advanced.md#nested-json-updates) - Updating JSON fields