zanith

JSONB Operations

PostgreSQL's JSONB is first-class in Zanith. Extract values, check containment, test key existence — all through typed field references.

When to use JSONB

Not everything fits neatly into columns. User preferences, feature flags, form submissions, API responses — JSONB lets you store structured data without creating new tables for every shape. The tradeoff is less type safety (it's unknown by default), but more flexibility.

Defining a JSONB field

TS
const User = defineModel((m) => ({
name: 'User', table: 'users',
fields: {
...m.id(),
email: m.string(),
// Untyped JSON (flexible)
settings: m.json().nullable(),
// Typed JSON (if you know the shape)
preferences: m.json<{ theme: string; language: string }>().nullable(),
},
}));

Text extraction (→>>)

Extract a text value from a JSON key. This is the most common operation — it returns the value as a string, which you can then compare.

TS
// Find posts where metadata.type = 'article'
const articles = await db.post.query()
.where(({ post }) =>
post.metadata.jsonText('type').eq('article')
)
.execute();
 
// Generated SQL:
// WHERE "posts"."metadata"->>'type' = $1
// Params: ['article']

Containment (@>)

Check if a JSONB column contains a specific structure. This is powerful for filtering by nested values without extracting each key individually.

TS
// Find posts with { featured: true } in their metadata
const featured = await db.post.query()
.where(({ post }) =>
post.metadata.jsonContains({ featured: true })
)
.execute();
 
// Generated SQL:
// WHERE "posts"."metadata" @> $1::jsonb
// Params: [{ featured: true }]
 
// Works with nested structures too:
post.metadata.jsonContains({ config: { priority: 'high' } })

Key existence (?)

Check if a key exists in the JSON object — regardless of its value.

TS
// Find posts that have a 'tags' key in their metadata
const tagged = await db.post.query()
.where(({ post }) =>
post.metadata.jsonHasKey('tags')
)
.execute();
 
// Generated SQL:
// WHERE "posts"."metadata" ? $1
// Params: ['tags']

Operator reference

MethodPostgreSQL operatorUse case
.jsonText(key)->> 'key'Extract text value, then compare with .eq(), .contains(), etc.
.jsonPath(key)-> 'key'Extract JSON value (not text) for nested access
.jsonContains(obj)@> obj::jsonbCheck if JSON contains a structure
.jsonHasKey(key)? 'key'Check if a key exists in the JSON object

Combining with other filters

TS
// Published posts with type='article' and featured=true
const results = await db.post.query()
.with({ author: true })
.where(({ post, author }) =>
and(
post.published.eq(true),
post.metadata.jsonText('type').eq('article'),
post.metadata.jsonContains({ featured: true }),
author.role.eq('ADMIN')
)
)
.execute();

JSONB filters work seamlessly with regular field filters, relational filters, and logical combinators (and, or, not).