Reject Ambiguous AI JSON Before Automation With Python
An AI assistant can return something that looks like JSON but leaves your workflow with two conflicting labels or a confidence value that is not a number. A successful parse is not enough: Python’s default decoder accepts repeated keys and keeps the last value. Python documents this behavior.
Build a small offline gate that rejects ambiguity before a response reaches automation. You need Python 3.10 or later and no extra packages or model account. The fixtures below are synthetic parser tests, not measured AI responses.
1. Define the receiving contract
For a ticket-labeling prototype, require one object with exactly two fields:
{"label":"billing","confidence":0.8}
Allowed labels are billing, technical, and other. Confidence must be a finite number from 0 to 1, inclusive; booleans and numeric strings are not accepted. Limit each UTF-8 response to 16,384 bytes. These are our workflow rules, not universal JSON requirements.
RFC 8259 recommends unique object names and warns that duplicate handling varies. Its number grammar excludes NaN and Infinity. Reject those cases instead of guessing the model’s intention. As with SQL integrity constraints, make acceptance rules explicit before using the data.
2. Save the strict checker
Save this as check_ai_json.py in a fresh working directory. It reads one bounded response file and never executes model-written code or routes a ticket.
import json
import math
import sys
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"duplicate key: {key}")
result[key] = value
return result
def reject_constant(token):
raise ValueError(f"non-JSON constant: {token}")
def load_reply(path):
with open(path, "rb") as stream:
payload = stream.read(16_385)
if len(payload) > 16_384:
raise ValueError("response exceeds 16384 bytes")
try:
data = json.loads(payload.decode("utf-8"),
object_pairs_hook=unique_object,
parse_constant=reject_constant)
except json.JSONDecodeError as error:
raise ValueError("invalid JSON syntax") from error
if not isinstance(data, dict) or set(data) != {"label", "confidence"}:
raise ValueError("expected only label and confidence")
if type(data["label"]) is not str or data["label"] not in {
"billing", "technical", "other"
}:
raise ValueError("unknown label")
score = data["confidence"]
if type(score) not in (int, float):
raise ValueError("confidence must be a number, not bool or string")
if not 0 <= score <= 1 or not math.isfinite(score):
raise ValueError("confidence must be finite and between 0 and 1")
return data
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: python3 check_ai_json.py RESPONSE.json")
try:
result = load_reply(sys.argv[1])
except (OSError, ValueError, RecursionError) as error:
raise SystemExit(f"FAIL: {error}")
print(f"PASS: structure accepted; label={result['label']}")
The decoder hooks inspect original key pairs and reject non-JSON constants. Duplicate detection also catches escaped spellings of the same key. For numeric validation, Python booleans are integer subtypes, so the exact-type check deliberately excludes them.
The range check comes before isfinite, avoiding conversion of enormous out-of-range integers. The syntactically valid JSON number 1e400 can overflow the decoder’s default float representation; it must still fail our confidence policy.
3. Create positive and negative fixtures
Save this beside the checker as make_json_fixtures.py. Run it in the fresh directory: it creates json-demo and refuses to reuse an existing folder. The adversarial cases follow the same principle as small regex acceptance tests.
from pathlib import Path
root = Path("json-demo")
root.mkdir()
good = '{"label":"billing","confidence":0.8}'
cases = {
"good": good,
"duplicate": '{"label":"billing","label":"technical","confidence":0.8}',
"nan": good.replace("0.8", "NaN"),
"infinity": good.replace("0.8", "Infinity"),
"overflow": good.replace("0.8", "1e400"),
"boolean": good.replace("0.8", "true"),
"quoted": good.replace("0.8", '"0.8"'),
"extra": good[:-1] + ',"note":"hello"}',
"prose": good + " Here is your answer.",
"array": "[" + good + "]",
"escaped": r'{"label":"billing","la\u0062el":"other","confidence":0.8}',
}
for name, text in cases.items():
with (root / f"{name}.json").open("x", encoding="utf-8") as stream:
stream.write(text)
Run these commands in Bash. Leave set -e disabled because ten failures are intentional.
python3 make_json_fixtures.py
for case in good duplicate nan infinity overflow boolean quoted extra prose array escaped; do
python3 check_ai_json.py "json-demo/$case.json"
echo "exit=$?"
done
4. Validate the observed results
Locally tested with Python 3.11.4, good prints this to stdout, leaves stderr empty, and exits 0:
PASS: structure accepted; label=billing
Every other case exits 1 with empty stdout. Its stderr contains one line:
| Case | Stderr |
|---|---|
duplicate, escaped |
FAIL: duplicate key: label |
nan |
FAIL: non-JSON constant: NaN |
infinity |
FAIL: non-JSON constant: Infinity |
overflow |
FAIL: confidence must be finite and between 0 and 1 |
boolean, quoted |
FAIL: confidence must be a number, not bool or string |
extra, array |
FAIL: expected only label and confidence |
prose |
FAIL: invalid JSON syntax |
The Bash loop prints each exit status separately. All eleven input files remained unchanged during these checks. Require the valid fixture to pass and every negative fixture to fail before integrating the checker.
Passing says nothing about whether billing is the correct label. Measure that separately with a labeled dataset and a ticket-routing confusion matrix. A model-supplied confidence number is not automatically a calibrated probability.
5. Troubleshoot without silently repairing data
- Markdown wrappers or trailing explanations: request a raw JSON object again. Do not extract a convenient substring and ignore the rest.
- Duplicate keys: regenerate the response. Keeping one value silently discards conflicting evidence.
- Quoted numbers or booleans: fix the output contract rather than coercing them into numbers.
- Unexpected fields: version the receiving contract and update fixtures before allowing new keys.
- Still accepts duplicates with strict=True: that decoder option controls unescaped control characters, not key uniqueness. Use the hooks above.
Keep this gate separate from downstream actions. The byte cap is a tutorial safeguard, not a complete resource-isolation or security boundary. Production ingestion also needs appropriate resource limits and a review path for rejected responses.
Practical takeaway
Treat AI JSON as untrusted input. Reject ambiguous syntax and invalid field values first, then evaluate whether the accepted content is useful and correct. Keep the original response and rejection reason for debugging.
Related articles
Preview AI File-Renaming Plans Before Touching Files
Lack of Agreement on AI Rules in U.S., EU Gives China a Leg Up
15 Best AI Tools for Startup Founders
How AI Can Be Your HR Sidekick in Recruitment and Employee Engagement
AI Call Center Is No Longer a Myth but a Reality: 6 Steps for Painless Implementation
How to Use AI Brand Voice Generator to Preserve Channel-Specific Voices
Lightweight Agno Framework for Fast Multimodal AI Agent Creation
Your Easy-to-Follow Guide to Agentic AI Workflows
Find Train/Test Text Overlap Before Evaluating an AI Model
Anthropic's Public Benefit Fund: What Its 5% Share Pledge Means
AWS Seeks to Teach Executives About Generative AI
Top 11 Companies Hiring for AI Jobs in 2025
Latest articles
Evaluate AI Probability Forecasts With the Brier Score in Python
Find Train/Test Text Overlap Before Evaluating an AI Model
Preview AI File-Renaming Plans Before Touching Files
Test AI-Generated Regex with Small Python Fixtures
Test AI-Generated SQL with Small SQLite Fixtures
Evaluate AI Transcription Accuracy with Word Error Rate
Evaluate AI Ticket Routing With a Confusion Matrix
How to Evaluate RAG Retrieval With Recall@k and MRR
How to Evaluate AI Agents Before Deployment
AI Agent Memory vs. RAG: What to Store and What to Retrieve
Anthropic's Public Benefit Fund: What Its 5% Share Pledge Means