Query data inside JSON/JSONB columns using the fluent expression API.
Dext ORM supports querying JSON data stored in database columns. This is useful when you have semi-structured data that doesn't fit a fixed schema.
Use the [JsonColumn] attribute on string properties that store JSON:
type
[Table('UserMetadata')]
TUserMetadata = class
private
FId: Integer;
FName: string;
FSettings: string;
public
[PK, AutoInc]
property Id: Integer read FId write FId;
property Name: string read FName write FName;
[JsonColumn] // or [JsonColumn(True)] for JSONB on PostgreSQL
property Settings: string read FSettings write FSettings;
end;| Database | Recommended Column Type |
|---|---|
| PostgreSQL | JSONB (indexed) or JSON |
| SQLite | TEXT (requires JSON1 extension) |
| MySQL | JSON |
| SQL Server | NVARCHAR(MAX) |
Use the .Json('path') method on property expressions:
// Data: {"role": "admin", "theme": "dark"}
var Admins := Context.UserMetadata
.Where(Prop('Settings').Json('role') = 'admin')
.ToList;Generated SQL (PostgreSQL):
SELECT * FROM "UserMetadata"
WHERE "Settings" #>> '{role}' = :p1Access nested JSON structures using dot notation:
// Data: {"profile": {"details": {"level": 5}}}
var Result := Context.UserMetadata
.Where(Prop('Settings').Json('profile.details.level') = 5)
.ToList;Generated SQL (PostgreSQL):
SELECT * FROM "UserMetadata"
WHERE "Settings" #>> '{profile,details,level}' = :p1::text💡 Numeric values are automatically cast to TEXT for comparison when querying JSON.
Query records where a JSON key doesn't exist or is null:
// Find records without the "nonexistent" key
var Result := Context.UserMetadata
.Where(Prop('Settings').Json('nonexistent').IsNull)
.ToList;Generated SQL (PostgreSQL):
SELECT * FROM "UserMetadata"
WHERE ("Settings" #>> '{nonexistent}' IS NULL)- Uses
#>>operator for text extraction - Supports
JSONBtype with indexing and optimization - Automatic
::textcast when comparing with non-string values - Automatic
::jsonbcast on INSERT for[JsonColumn]properties
- Uses
json_extract()function - Requires SQLite compiled with
SQLITE_ENABLE_JSON1 - Enable in
Dext.inc:{$DEFINE DEXT_ENABLE_SQLITE_JSON}
- Uses
JSON_EXTRACT()andJSON_UNQUOTE()functions - Native
JSONcolumn type
- Uses
JSON_VALUE()function - Store in
NVARCHAR(MAX)columns
When using [JsonColumn(True)] (UseJsonB = True), the ORM automatically casts string values to jsonb during INSERT:
var Meta := TUserMetadata.Create;
Meta.Name := 'Admin';
Meta.Settings := '{"role": "admin"}'; // String with JSON content
Context.UserMetadata.Add(Meta);
Context.SaveChanges;Generated SQL:
INSERT INTO "UserMetadata" ("Name", "Settings")
VALUES (:p1, :p2::jsonb)procedure TestJsonQueries(Context: TMyDbContext);
var
User: TUserMetadata;
Results: IList<TUserMetadata>;
begin
// Insert test data
User := TUserMetadata.Create;
User.Name := 'Admin';
User.Settings := '{"role": "admin", "permissions": ["read", "write"]}';
Context.UserMetadata.Add(User);
User := TUserMetadata.Create;
User.Name := 'Guest';
User.Settings := '{"role": "guest", "permissions": ["read"]}';
Context.UserMetadata.Add(User);
Context.SaveChanges;
Context.DetachAll;
// Query by JSON property
Results := Context.UserMetadata
.Where(Prop('Settings').Json('role') = 'admin')
.ToList;
Assert(Results.Count = 1);
Assert(Results[0].Name = 'Admin');
end;- Type Coercion: JSON extraction returns TEXT; numeric comparisons require cast
- Indexing: Only PostgreSQL JSONB supports native indexing
- Complex Queries: Array indexing and advanced operators not yet supported
- SQLite: Requires custom-compiled sqlite3.dll with JSON support