@json — JSON Column Serialization
Automatically serializes JavaScript objects to JSON strings on write and parses them back on read. The column type is TEXT (or equivalent) in all databases.
Syntax
src/interfaces.ts
// @json
meta?: Record<string, unknown>;
settings?: Record<string, unknown>;Usage
src/auth.ts
const user = await User.insert({
name: "Alice",
meta: { theme: "dark", notifications: true, score: 42, tags: ["orm", "typescript"] },
});
console.log(user.meta.theme); // "dark"
console.log(user.meta.tags); // ["orm", "typescript"]
// Fetch — auto-parsed back to object
const fetched = await User.get({ id: user.id });
console.log(fetched.meta.theme); // "dark"
console.log(fetched.meta.tags[0]);// "orm"Nested Objects & Partial Updates
src/auth.ts
// Deep nesting works
await user.update({ meta: { nested: { a: 1, b: [2, 3] } } });
const updated = await User.get({ id: user.id });
console.log(updated.meta.nested.b); // [2, 3]
// ⚠️ Partial update replaces the ENTIRE object
await User.update({ id: user.id }, { meta: { theme: "light" } });
// Result: meta = { theme: "light" } — nested and score are GONE
// Use instance update for merge behavior:
await user.update({ meta: { ...user.meta, score: 99 } });Errors
| Error | Cause | Fix |
|---|---|---|
Invalid JSON in column "meta" | Stored value is not valid JSON | Manually fix the database value or use raw SQL to update |
Field returns null instead of object | Column contains SQL NULL | Set a default value in the interface: meta?: Record<string, unknown> = |
Conventions
- Use
Record<string, unknown>for flexible objects, or a typed interface for structured data - update() replaces the entire JSON column — always spread existing values if you want to preserve fields
- Use instance
.update()if you want to merge new fields with existing ones programmatically - JSON columns are not indexed by default — use
@indexif you need to query on JSON paths - Inferred models (without schema) auto-detect
Record<string, unknown>fields as JSON