JSON
Ormed has first-class JSON support across models and query builder APIs.
note
These examples focus on JSON operations only. They assume you already have a working DataSource.
1. Define JSON Model Fields
(table: 'users')
class User extends Model<User> {
const User({required this.id, required this.email, this.metadata});
(isPrimaryKey: true, autoIncrement: true)
final int id;
final String email;
// Use the built-in `json` cast key for JSON objects.
(cast: 'json')
final Map<String, Object?>? metadata;
}
2. Query JSON Data
In Dart strings, $ is interpolation — if you write selectors with $.path, use raw strings (r'...'), or omit the $ prefix (metadata->>mode).
Array indexes can be addressed as either:
items[0](explicit), oritems.0(shorthand; normalized toitems[0])
If you need a numeric object key, use bracket quoting: items["0"].
- Helpers
- Selector syntax
- Indexing
Future<List<User>> jsonWhereHelpers(DataSource dataSource) {
return dataSource
.query<$User>()
.whereJsonContains('metadata', 'Dart', path: r'$.skills')
.whereJsonContainsKey('metadata', r'$.flags')
.whereJsonLength('metadata->tags', '>=', 2)
.whereJsonOverlaps('metadata->skills', ['Dart', 'Go'])
.get();
}
Future<List<User>> jsonWhereSelectorSyntax(DataSource dataSource) {
return dataSource
.query<$User>()
// `->` extracts JSON, `->>` extracts text for comparisons/sorting.
// If you include `$.path` in a Dart string, prefer raw strings `r'...'`.
.where(r'metadata->>$.mode', 'dark')
// You can also omit the `$` prefix (`mode` normalizes to `$.mode`).
.where('metadata->>mode', 'dark')
.where(r'metadata->$.featured', true)
.orderBy('metadata->>createdBy')
.get();
}
Future<List<User>> jsonWhereIndexing(DataSource dataSource) {
return dataSource
.query<$User>()
// Array index addressing: `items[1]` and `items.1` are equivalent.
.where(r'metadata->>$.meta.profile.items[1].label', 'b')
.where(r'metadata->>$.meta.profile.items.1.label', 'b')
// Numeric object keys are different: use bracket quoting.
.whereJsonContainsKey('metadata', r'$.items["0"]')
.get();
}
3. Update JSON Values
- Query update
- Tracked model
Future<void> jsonUpdateQuery(DataSource dataSource) async {
await dataSource.query<$User>().whereEquals('id', 1).update(
{'email': 'updated@example.com'},
jsonUpdates: (_) => [
JsonUpdateDefinition.path('metadata', r'$.meta.count', 5),
JsonUpdateDefinition.selector('metadata->mode', 'light'),
// Update JSON array elements (both forms normalize the same way).
JsonUpdateDefinition.path(
'metadata',
r'$.meta.profile.items[1].label',
'b2',
),
JsonUpdateDefinition.path(
'metadata',
r'$.meta.profile.items.0.label',
'a2',
),
JsonUpdateDefinition.patch('metadata', {
'flags': {'beta': true},
}),
],
);
}
Future<void> jsonUpdateModel($User user) async {
// Queue JSON updates on the tracked model, then save.
user.jsonSetPath('metadata', r'$.meta.count', 5);
user.jsonSet('metadata->mode', 'light');
user.jsonSetPath('metadata', r'$.meta.profile.items.0.label', 'a2');
user.jsonPatch('metadata', {
'flags': {'beta': true},
});
await user.save();
}
Practical Rules
- Use helper methods (
whereJsonContains,whereJsonLength,whereJsonContainsKey) for portability and readability. - Use raw selector syntax for advanced expressions or mixed SQL/JSON predicates.
- Prefer path/patch updates over full document replacement to reduce write size.
Verify Your Setup
- JSON model fields round-trip through your driver.
- JSON selector filters return expected rows for both object keys and array indexes.
- Partial JSON updates preserve unrelated keys.