Skip to content

AI JSON output: validate shape before business actions

A parseable object is not evidence that its fields are complete, its values make sense or the requested action is authorized.

Dopstack Engineering

· 7 min read

A candidate JSON object passes syntax and schema gates, then reaches separate application-state and permission decisions.

Quick answer

Treat AI-generated JSON as untrusted input. Parse it, validate it against an explicit schema, then apply business rules and authorization before any side effect. Required fields, extra properties, string constraints and conditional requirements need deliberate configuration. Schema compliance proves only the contract you encoded, not that the proposed action is correct.

AI-generated JSON is a candidate input, not permission to act. Parse it, validate its structure, then check business rules and authorization using trusted application state. A structurally valid object can still name the wrong account or propose an operation the caller is not allowed to perform.

The parser accepts it. Why should the service reject it?#

Use an illustrative ticket-routing assistant. It proposes an action and a ticket identifier. The application should accept only an assign_ticket proposal for a non-empty identifier and a member identifier. It should reject extra fields that pretend to grant authority. None of those checks establishes that the ticket belongs to this workspace or that the caller may assign it.

ticket-proposal.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "action": { "enum": ["assign_ticket"] },
    "ticket_id": { "type": "string", "minLength": 1 },
    "member_id": { "type": "string", "minLength": 1 }
  },
  "required": ["action", "ticket_id", "member_id"],
  "additionalProperties": false
}

The required list makes omission a failure; declaring the properties alone would not. additionalProperties: false rejects undeclared fields at this object boundary. The example is intentionally flat: composition and nested objects need their own review, and validators must support the selected dialect. JSON Schema’s object and string references explain these constraints. [1][2]

illustrative-candidates.json
[
  { "action": "assign_ticket", "ticket_id": "T-42" },
  { "action": "assign_ticket", "ticket_id": "T-42", "member_id": "M-7", "role": "admin" },
  { "action": "assign_ticket", "ticket_id": "T-42", "member_id": "M-7" }
]

The first candidate fails because member_id is missing. The second fails because role is not part of the contract. The third passes this shape check. The server must still load T-42 in the authenticated workspace, establish that M-7 is an eligible member and authorize the caller. Those are application decisions. No string inside this object can stand in for them.

Conceptual illustration

Passing one check does not skip the next

01

Parse

Readable JSON

02

Schema

Expected fields and types

03

Application state

Ticket and member are valid here

04

Authorization

Authenticated caller may assign

Conceptual trust layers for a proposed ticket assignment; each layer can reject independently.

Which contract did the object actually pass?#

JSON Schema’s object reference explains two defaults that matter here: leaving a property out can be valid, and additional properties are allowed unless constrained. Merely listing familiar property names does not establish the complete contract a production workflow needs. Requiredness and handling of unknown keys are deliberate schema decisions. [1]

Start by defining what a success result means. Distinguish successful parsing, structural validation and permission to act. Keep each result in the execution record, with the schema version used. If a team says the model returned valid JSON, ask which boundary was tested and what the application did with the object afterward.

BoundaryExample questionFailure behavior
SyntaxCan the response be parsed?Reject or request a bounded format repair
SchemaAre the required properties and types present?Return structured validation errors
Business rulesIs the operation consistent with current state?Reject the proposed operation with a reason
AuthorizationMay this caller perform this action here?Deny the side effect
ExecutionDid the accepted operation complete?Use the application’s recovery contract
Keep validation outcomes separate

How do you make the shape explicit?#

Start with the fields the consumer actually uses. Decide required properties, types and unknown-key behavior. A schema that accepts omission cannot establish that a downstream assumption is present. JSON Schema also provides string length and pattern constraints; choose those according to the receiving system rather than a model’s preferred phrasing. [1][2]

Our recommendation is to build test objects before tuning a generation prompt. Include a missing required value, an incorrect type, an extra property and an otherwise valid object. Make the intended accept or reject result explicit. That forces the application owner to decide the contract instead of discovering it through whichever output the model happens to produce.

Treat normalization as its own documented operation. Trimming whitespace, resolving aliases or converting a representation can be useful, but silently converting every invalid value obscures input quality. Preserve the original non-sensitive diagnostic representation where appropriate, and record which transformation produced the accepted object.

Which rules belong beyond basic structure?#

JSON Schema can express conditional property requirements. Its dependentRequired keyword maps a present property to other required properties, and those dependencies are not automatically bidirectional. If both directions matter, the contract must express that intention. Check the schema dialect supported by your validator. [3]

Other decisions depend on live application state. Our examples of questions to ask are whether the referenced resource still exists, whether the transition remains available and whether the caller owns the relevant scope. Those checks should use authoritative application data. A field containing an approved-looking identifier is not evidence that the approval took place.

Keep authorization outside the generated object’s authority. The object may propose a target or operation, but the application decides which authenticated principal and permissions apply. Do not let a model-supplied role string replace the server’s access decision. This follows the same boundary discussed in AI agent tool permissions.

Put the effect behind the application decision#

Keep the generation step outside the trusted side-effect path. The following pseudocode expresses the order of checks; it is not a library-specific validator API. The authenticated principal and workspace come from the request context, not the model response.

proposal-handler.pseudocode
candidate = parse_json(model_response)
validate_schema(candidate, ticket_proposal_schema)

ticket = load_ticket_in_workspace(request.workspace, candidate.ticket_id)
member = load_member_in_workspace(request.workspace, candidate.member_id)
authorize(request.principal, "assign_ticket", ticket)
check_assignment_rules(ticket, member)

assign_ticket_with_recovery_contract(ticket, member, request.operation_id)

The last line needs the application’s own concurrency and retry design. If assignment races with another state change, the service may need to recheck conditions inside the transaction or use a conditional update. If the operation has a remote side effect, it needs the provider’s supported idempotency or reconciliation path. A schema does not make a multi-system operation atomic.

CategoryExample in this workflowReasonable response
SyntaxResponse cannot be parsedBounded representation repair
Shapemember_id absentPrecise schema error
Business stateTicket is already closedReturn a current-state rejection
AuthorizationCaller cannot assign this ticketDeny; do not prompt for a stronger role
ExecutionAccepted remote action has an uncertain resultReconcile with the operation reference
Different rejection categories need different responses

One common mistake is a universal repair loop that sends every error back to the model. A missing field can sometimes be corrected from information the user already supplied. A denied permission cannot be corrected by inventing another role string. A closed ticket cannot be made open by emitting a different status. Route those failures to the service or the user decision that actually owns them.

How should invalid output be repaired?#

Distinguish a format defect from an unavailable business operation. A bounded retry can ask for an object that satisfies a known shape. It should not encourage the generator to invent a missing authorization, current balance or resource status merely to make the validator accept the request.

  1. Return a precise failure categoryIdentify whether parsing, schema, business state or authorization failed. Avoid sending sensitive application context into an unconstrained repair prompt.
  2. Bound the retryLimit attempts and retain the failure reason. A repeated inability to satisfy the contract should become a visible failure, not an infinite loop.
  3. Revalidate the repaired objectRun the full sequence again. The fact that a response was produced after feedback does not confer permission to skip later checks.
  4. Protect the side effectExecute only after the application has accepted the current request. Use a separate idempotency and recovery policy for retries of the action itself.

Before rollout, test the cases that are easy to confuse: an empty string versus an absent property, an unknown key versus a known key with the wrong type, and a structurally valid operation that the caller cannot perform. Keep those distinctions in a production-readiness review such as moving an AI workflow beyond its prototype. No provider-specific structured-output guarantee is assumed here.

Test rejection before polishing the prompt#

Keep a small fixture set beside the schema: omitted member, empty ticket, wrong type, unexpected role, valid shape with a forbidden target and a fully accepted operation. The last two deliberately share the same shape. That pair proves why schema acceptance and application acceptance must remain separate. Repeat the fixtures when the prompt, schema or validator changes.

Make version changes explicit. Adding a required property changes what old candidates can satisfy; silently coercing or dropping fields can hide a contract mismatch. Record which schema version evaluated the candidate and which boundary rejected it. Use sanitized diagnostics and keep rejected confidential content out of routine logs.

The target is understandable failure. An operator should be able to tell whether a bad result came from generation, validation, stale business state or execution. That is more actionable than one red “AI failed” status, and it gives prompt work a narrower purpose: improve the candidates without granting them authority.

AI output validation questions#

Does valid JSON mean the fields are complete?
No. Parsing validates the syntax, while your structural contract decides which fields must exist. JSON Schema permits omitted properties unless they are made required, so inspect the actual schema rather than relying on the object looking familiar. [1]
Can I reject unexpected keys?
Yes, JSON Schema provides controls for additional properties. Decide the behavior deliberately and check how schema composition and the supported dialect affect it. Do not assume that declaring properties alone rejects every unknown key. [1]
Can conditional fields be validated?
JSON Schema includes conditional requirements such as dependentRequired. Its dependencies are directional rather than automatically symmetric. Test both the triggering case and the reverse case so the intended relationship is explicit. [3]
Can a repair retry bypass authorization?
No. Our proposed workflow revalidates every repaired candidate and applies authorization from the authenticated application context. A retry may repair representation, but cannot manufacture a permission or make an unavailable operation valid.

Technologies

  • JSON Schema

Sources

  1. [1]
    JSON Schema: object, JSON Schema

    Primary documentation read on 2026-10-17; verify applicability to your deployed version.

  2. [2]
    JSON Schema: string, JSON Schema

    Primary documentation read on 2026-10-17; verify applicability to your deployed version.

  3. [3]
    Conditional schema validation, JSON Schema

    Primary documentation read on 2026-10-17; verify applicability to your deployed version.

Written by

Dopstack Engineering

Engineering team

The engineering team at Dopstack. Illustrative scenarios are composites written to explain a pattern, not accounts of client work.

All articles Website

Get new articles by email

Engineering notes on AI, cloud and SaaS. Occasional, never sold or shared.

Send me new Dopstack Insights articles by email. I can unsubscribe at any time. We’ll email you once to confirm. Privacy

Continue reading