Dynamic JSON Query
Evidence status: the Java parser and generated request shape have been reviewed, but the current generated Java baseline is blocked before execution. The allowlist, tenant-isolation, resource-limit, and provider behavior below still require integration tests before production adoption. No equivalent Rust JSON-query contract is claimed by this recipe.
Problem
Accept flexible filters, ordering, and pagination from a search form without creating one endpoint per filter combination—and without letting untrusted JSON control tenant scope, permissions, soft-delete behavior, or query cost.
When to Use It
Use findWithJsonExpr(...) for a bounded search screen whose permitted fields
and operations are known by the application.
Do not use it:
- as a public arbitrary-query language;
- to choose tenant, merchant, user, or permission scope;
- when a fixed generated query expresses the operation more clearly;
- before defining request-size, field, sort, page-size, and chain-path limits.
Inputs and Trust Boundary
For an illustrative order-search screen, allow only a small contract:
{
"code": "ORD-2026",
"status": ["NEW", "SHIPPED"],
"createTime": [1714521600000, 1717200000000],
"_orderBy": {
"field": "id",
"useAsc": false
},
"_page": 1,
"_pageSize": 20
}
Keep these outside the JSON and derive them from trusted server context:
- tenant or merchant identity;
- current-user and permission constraints;
- soft-delete/version constraints;
- required relation shape;
- application maximum page size and request size.
The runtime recognizes fields on the generated request and ignores unknown self-field filters. That behavior is not input validation: silently ignoring a misspelled or forbidden field can produce a broader result than the caller expected. Reject fields outside the endpoint allowlist before merging JSON.
Java Path
Validate and normalize the request at the handwritten API boundary, then merge it into a generated request that still applies trusted constraints:
public SmartList<Order> searchOrders(
CustomUserContext ctx,
String rawParams) {
String params = OrderSearchPolicy.validateAndNormalize(rawParams);
return Q.orders()
.selectAll()
.findWithJsonExpr(params)
.filterByMerchant(ctx.getMerchant())
.orderByIdDescending()
.comment("Query merchant orders")
.purpose("Render the bounded order search results")
.executeForList(ctx);
}
Q.orders(), selectors, filters, and ordering methods are model-generated.
Inspect the current generated request before adapting this shape.
The application policy should, at minimum:
- reject malformed or oversized JSON;
- reject keys outside the endpoint field/control allowlist;
- allowlist sort fields and chain paths independently;
- validate value types and date-range order;
- require positive page numbers and cap
_pageSizeand_size; - reject tenant, merchant, permission, deleted-state, and system-only fields;
- return the normalized JSON only after every rule passes.
The size and page limits are application policies, not TeaQL defaults. Choose them from the endpoint's latency, provider, and response-size requirements.
Chain Fields
A chain filter such as customer.name requires the corresponding child request
to already exist in the generated request graph:
Q.orders()
.selectCustomer(Q.customers().selectName())
.findWithJsonExpr(validatedParams)
.filterByMerchant(ctx.getMerchant())
.comment("Query merchant orders by customer")
.purpose("Render authorized customer search results")
.executeForList(ctx);
Allowlist the full chain path. Do not accept arbitrary traversal depth or add relations merely because the caller named them.
Operator Semantics to Test
The reviewed Java helper maps value shapes to operators. Important cases include:
| Input shape | Reviewed interpretation | Risk to test |
|---|---|---|
| String | CONTAIN by default | A caller may expect exact equality. |
| Number or boolean | EQUAL | Numeric conversion and invalid types. |
| String array | IN | Empty and very large lists. |
| Two-value date/time array | Range | Units, order, timezone, and nulls. |
__is_null__ / __is_not_null__ | Null predicate | Whether the field may be queried. |
_orderBy | Generated self-field ordering | Stable ordering and allowlisted fields. |
_page / _pageSize | Page window | Boundary pages and maximum size. |
Use a fixed generated equality filter for security-sensitive or exact business
conditions rather than relying on a string's dynamic CONTAIN behavior.
Verification
Run provider-backed integration tests with at least two tenants or merchants:
- A valid allowlisted filter returns only matching in-scope rows.
- An unknown, misspelled, forbidden, or system field is rejected.
- JSON that names another tenant still cannot return its rows.
- Oversized input, page size,
INlist, and chain depth are rejected. - Exact, contains, null, range, sort, first/last/empty page cases match the documented semantics.
- Nested chain filters cannot traverse an unselected or unapproved relation.
- SQL/query diagnostics show bounded execution for the supported provider.
- The same trusted constraints apply to any matching count query.
Record the runtime, generated API, provider/database version, test command, and
query diagnostics before changing this page to verified.
Failure Modes
| Symptom | Most likely cause | Smallest credible fix |
|---|---|---|
| String search is broader than expected | Strings default to CONTAIN. | Use a generated exact filter or a validated exact-value shape. |
| Forbidden rows appear | Trusted scope was omitted or tested only through JSON. | Apply server-derived scope and add a cross-tenant integration test. |
| Filter has no effect | Field is unknown, misspelled, or the chain request is absent. | Reject it during validation and inspect the generated request graph. |
| Query is slow or response is large | Page, list, relation, or sort limits are missing. | Enforce endpoint limits and inspect provider query diagnostics. |
| Generated method is missing | The example does not match the model. | Read generated Q and request sources; never patch generated output. |