开始前,会这些就够了
- 知道 JSON 对象由字段和值组成
- 先读 参数格式与业务规则
- Python 3.10+;需要按
requirements.txt安装 jsonschema 4.26.0 - 先读 让结构化结果通过三层校验:先区分能解析、符合结构与业务允许,再查看格式检查的实际开关。
助手交来 visit_date 为 2024-02-31 的查询参数。规则里已经写了 type 为 string、format 为 date,校验函数却没有报错。先别判断日期库算错了:这次运行真的启用了日期检查吗?
本篇使用真实的 Python jsonschema 4.26.0,不用手写函数假装实现 JSON Schema。所有日期来自固定虚构案例;程序只做查询前的输入检查,不调用模型、不查真实日历,也不办理预约。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本;实测 Windows / Python 3.14.0,依赖 jsonschema==4.26.0;Python 3.10 仅做语法兼容检查。首次创建隔离环境并安装固定版本依赖需要联网;之后运行示例不访问网络。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 6 个文件包里有什么
- demo.py:完整可运行源码
- README.md:运行步骤与练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和运行验证记录
- SHA256SUMS.txt:文件摘要
- requirements.txt:固定版本依赖
在这个文件夹打开 PowerShell,逐行运行
python --version
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -B demo.py解压后,在含 demo.py 的目录打开终端。命令分别回车执行。真实 jsonschema 4.26.0 的日期格式配置与固定虚构窗口预检;未调用模型、日历或预约服务;不验证所有 format。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
写了 date,检查器就一定会拒绝 2 月 31 日吗?
正在准备实验控件…
这里回放 CPython 3.14.0、jsonschema 4.26.0 对固定案例的实际结果。可切换案例与规则观察差别;网页没有运行通用 JSON Schema 校验器,也没有执行查询或预约。下载完整 Python 程序可在本地复验。
先把同一份日期参数交给两种配置
工具输入只允许 visit_date 一栏,要求它存在且为字符串,其他字段被拒绝。两组验证器使用完全相同的 Schema:一组直接创建 Draft202012Validator,另一组额外传入 FormatChecker。
默认组接受 2024-02-31,开启组拒绝它;合法的 2024-02-29 在两组都通过。数字 42、缺字段和多余字段在两组都被拒绝,因为这些规则由 type、required 和 additionalProperties 负责,开关没有关闭这些检查。
因此,“开启前通过”不代表完全没做校验。需要具体到是哪一种规则没有参与,而不是把所有失败都笼统归成模型没有按格式回答。
format 可以说明含义,也可以参与拒绝输入
JSON Schema 2020-12 把格式标注与格式断言分开。标注说明这段字符串应被当成什么,例如日期;断言则参与判断这个值是否合格。写在 Schema 里的说明,不一定已经变成当前验证器执行的拒绝条件。
Python jsonschema 的文档明确说明:没有传入 format_checker 时,format 只提供信息,不进行格式检查。本实验正是这个已固定版本的默认行为;不能未经核对就推广为所有语言、草案和框架都采用同一配置。
标准还定义了可选的 Format-Assertion 词汇。为某个实现打开若干格式检查,不等于它因此完整支持该词汇要求的全部格式。这里始终区分标准规则、库的具体配置和实际跑过的输入。
打开 date 检查后,再看它到底检查什么
prepare_date_validator 为本工具启用 date 检查。实测中,非闰年的 2023-02-29、根本不存在的 2024-02-31,以及缺少补零的 2024-2-9 都被拒绝。RFC 3339 的 full-date 定义与月份表给出了这些预期的依据。
date 格式不替代 type。代码另建一份只有 format=date 的规则,数字 42 仍通过;本工具必须保留 type=string,才能明确拒绝非字符串。缺字段也由 required 负责,不是日期检查器替你发现。
错误记录使用校验器给出的关键字和路径。非法日期对应实例中的 visit_date,以及 Schema 中的 properties、visit_date、format。程序核对这些结构化信息,没有依赖整句英文错误文案永远不变。
格式名拼成 dtae,开关仍然救不了它
再把 date 拼成 dtae。它仍是字符串,所以 check_schema 可以接受这份规则;但当前 FormatChecker 不认识这个名称,会让这一项通过。于是同一个 2024-02-31 又被接纳了。
本工具在启动前检查 visit_date 声明的格式名是否已注册到所用检查器。名称拼错时,直接报 unsupported_format:dtae;不能等到输入校验结束,才把“没有发现错误”当成“日期已核对”。这段预检只服务于当前固定的一栏合同,不是任意嵌套 Schema 的通用扫描器。
格式名为数字 7 则是另一类问题:Schema 自己不合规则,check_schema 抛出 SchemaError。输入日期不合格产生 ValidationError。两者应该分别定位到配置问题与输入问题,避免要求模型去修复应用写错的规则。
日期确实存在,也不一定在本次允许范围里
案例另行约定,只能准备查询 2024-02-20 至 2024-03-10,含两端。这是写死的虚构练习窗口,不取电脑今天的日期,每次复跑都应得到相同结果。
2024-03-11 和 2024-02-19 都是合法日期,格式检查当然通过;业务窗口检查随后返回 outside_fixed_window。对于格式或结构已经失败的输入,业务检查返回 not_checked,避免把“尚未检查”误写成“日期可用”。
ready_for_lookup 只表示满足这份查询前约定。它没有证明现实中有空位、用户有权限或查询已经完成。不同规则可以放在不同层实现,但最终报告必须说清实际通过的是哪一层。
安装固定依赖后,运行完整的九行对照
本程序需要第三方库。下载包提供 requirements.txt,只固定 jsonschema==4.26.0;先在自己的独立虚拟环境安装依赖,再用该解释器运行 demo.py。安装可能联网,实验运行只使用本地固定数据和已安装的包。
2026-10-23 已在 Windows、Python 3.14.0、jsonschema 4.26.0 上运行,53 项检查通过。九组主案例之外,还核对错误路径、未知格式、SchemaError、缺少 type 的反例以及输入未被修改。
终端输出和 report.json 来自真实库执行。下载包的 verification.json 记录运行版本与源码摘要;代码不包含完整依赖环境,迁到另一台电脑应重新安装 requirements.txt,而不是复制本机虚拟环境。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Real jsonschema validation of fixed fictional tool inputs; no network or booking."""
from copy import deepcopy
from datetime import date
from importlib.metadata import version
import json
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
from jsonschema.exceptions import SchemaError, ValidationError
EXPECTED_VERSION = "4.26.0"
START = date(2024, 2, 20)
END = date(2024, 3, 10)
checks = 0
def check(condition, message):
global checks
if not condition:
raise AssertionError(message)
checks += 1
def schema_for(format_name="date"):
return {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {"visit_date": {"type": "string", "format": format_name}},
"required": ["visit_date"],
"additionalProperties": False,
}
def prepare_date_validator(schema):
"""Preflight this fixed one-field tool contract, not arbitrary remote schemas."""
Draft202012Validator.check_schema(schema)
checker = FormatChecker(formats=["date"])
name = schema["properties"]["visit_date"]["format"]
if name not in checker.checkers:
raise ValueError("unsupported_format:" + name)
return Draft202012Validator(schema, format_checker=checker)
def business_status(payload, validator):
if not validator.is_valid(payload):
return "not_checked"
day = date.fromisoformat(payload["visit_date"])
return "ready_for_lookup" if START <= day <= END else "outside_fixed_window"
def main():
installed = version("jsonschema")
check(installed == EXPECTED_VERSION, "install requirements.txt first")
schema = schema_for()
original = deepcopy(schema)
Draft202012Validator.check_schema(schema)
default = Draft202012Validator(schema)
enabled = prepare_date_validator(schema)
cases = [
("valid_leap_day", {"visit_date": "2024-02-29"}, True, True, "ready_for_lookup", []),
("impossible_day", {"visit_date": "2024-02-31"}, True, False, "not_checked", ["format"]),
("non_leap_year", {"visit_date": "2023-02-29"}, True, False, "not_checked", ["format"]),
("not_zero_padded", {"visit_date": "2024-2-9"}, True, False, "not_checked", ["format"]),
("wrong_type", {"visit_date": 42}, False, False, "not_checked", ["type"]),
("missing", {}, False, False, "not_checked", ["required"]),
("extra_field", {"visit_date": "2024-02-29", "book": True}, False, False,
"not_checked", ["additionalProperties"]),
("too_late", {"visit_date": "2024-03-11"}, True, True, "outside_fixed_window", []),
("too_early", {"visit_date": "2024-02-19"}, True, True, "outside_fixed_window", []),
]
report = []
print("jsonschema=" + installed)
print(f"fictional_window={START.isoformat()}..{END.isoformat()}")
for name, payload, want_default, want_enabled, want_business, want_errors in cases:
before = deepcopy(payload)
without = default.is_valid(payload)
with_format = enabled.is_valid(payload)
business = business_status(payload, enabled)
errors = sorted({str(error.validator) for error in enabled.iter_errors(payload)})
check(without is want_default, name + " default")
check(with_format is want_enabled, name + " enabled")
check(business == want_business, name + " business")
check(errors == want_errors, name + " error keywords")
check(payload == before, name + " input unchanged")
report.append(dict(case=name, payload=payload, default=without, enabled=with_format,
business=business, error_keywords=errors))
print(f"{name}: default={without}; enabled={with_format}; business={business}")
try:
enabled.validate({"visit_date": "2024-02-31"})
except ValidationError as error:
check(list(error.absolute_path) == ["visit_date"], "instance error path")
check(list(error.absolute_schema_path) == ["properties", "visit_date", "format"],
"schema error path")
print("invalid_date_error: instance=/visit_date; schema=/properties/visit_date/format")
else:
raise AssertionError("invalid date accepted")
typo = schema_for("dtae")
Draft202012Validator.check_schema(typo) # A format name is a string, not an enum.
unknown = Draft202012Validator(typo, format_checker=FormatChecker(formats=["date"]))
check(unknown.is_valid({"visit_date": "2024-02-31"}), "unknown format is not checked")
try:
prepare_date_validator(typo)
except ValueError as error:
check(str(error) == "unsupported_format:dtae", "contract preflight rejects typo")
else:
raise AssertionError("unknown format configured")
print("unknown_format=dtae: library_accepts=True; tool_preflight=rejects")
format_only = Draft202012Validator({"format": "date"}, format_checker=FormatChecker())
check(format_only.is_valid(42), "format is not a string type assertion")
malformed = schema_for(7)
try:
Draft202012Validator.check_schema(malformed)
except SchemaError:
check(True, "numeric format value is a schema error")
else:
raise AssertionError("malformed schema accepted")
check(schema == original, "schema unchanged")
print("format_only_number=True; nonstring_format_value=SchemaError")
print(f"PASS checks={checks}")
Path("report.json").write_text(json.dumps({
"jsonschema": installed, "scope": "fixed fictional date precheck; no real operation",
"window": [START.isoformat(), END.isoformat()], "cases": report, "checks": checks,
}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-23 使用 Python 3.10 或更新版本;实测 Windows / Python 3.14.0,依赖 jsonschema==4.26.0;Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。真实 jsonschema 4.26.0 的日期格式配置与固定虚构窗口预检;未调用模型、日历或预约服务;不验证所有 format。
运行后对照结果
jsonschema=4.26.0
fictional_window=2024-02-20..2024-03-10
valid_leap_day: default=True; enabled=True; business=ready_for_lookup
impossible_day: default=True; enabled=False; business=not_checked
non_leap_year: default=True; enabled=False; business=not_checked
not_zero_padded: default=True; enabled=False; business=not_checked
wrong_type: default=False; enabled=False; business=not_checked
missing: default=False; enabled=False; business=not_checked
extra_field: default=False; enabled=False; business=not_checked
too_late: default=True; enabled=True; business=outside_fixed_window
too_early: default=True; enabled=True; business=outside_fixed_window
invalid_date_error: instance=/visit_date; schema=/properties/visit_date/format
unknown_format=dtae: library_accepts=True; tool_preflight=rejects
format_only_number=True; nonstring_format_value=SchemaError
PASS checks=53
接入 Agent 时,把规则与执行配置一起验收
查看工具 Schema 时,同时记录使用哪个草案、哪个验证器版本、是否启用格式检查,以及业务还补了哪些条件。只把 Schema 放进模型提示中,并不能证明应用入口真正执行了这些规则。
通用 JSON Schema 语义与具体模型服务接受的结构化输出子集是两层问题。本篇验证的是应用侧 Python 库;没有检验任何模型服务支持哪些关键字,也没有测它生成参数的准确率。
有些格式还依赖额外包,不同名称的支持程度也可能不同。当前示例只要求并实际测试 date,不以一个日期案例宣称所有格式都可靠;读者新增格式后,需要为那个格式准备独立反例。
记住这三点
- format 声明与实际格式检查是两步;本例需要显式传入
FormatChecker。 - 未知格式可能被忽略,
check_schema也不能替代本工具的格式支持预检。 - 结构、日期格式、业务窗口和实际执行结果应分别确认。
别把例子用过头
- 看到 format=date 就假定当前库已经检查日期。
- 格式名拼错后,把默认通过误当成支持该格式。
- 用 format 代替 type 或 required。
- 把虚构窗口内的
ready_for_lookup报成真实预约成功。
自己试一小步
给另一个只读工具设计 report_date 输入,怎样证明它确实拒绝无效日期并遵守自己的固定查询窗口?
- 自行选定一个固定窗口,加入普通年份和世纪年份的二月日期;运行前先写预期。
- 分别准备缺字段、错误类型、格式名拼错、日期合法但超出业务窗口的输入。
- 实现自己的错误报告,同时标明输入路径、失败规则,以及哪些后续检查没有执行。
做到这些,就算完成
- 预期来自日期规则和明确业务约定,没有用程序自己的输出当答案。
- 能分别发现未启用格式检查与格式名称没有注册。
- 报告没有把校验通过扩写成真实查询或操作已经完成。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- JSON Schema 2020-12:format 的两种词汇与日期定义 (新标签页)↗
区分 Format-Annotation 与 Format-Assertion,说明默认配置、未知名称以及 date 对应 RFC 3339 full-date。
- jsonschema 4.26.0:开启格式检查 (新标签页)↗
format_checker默认不启用;FormatChecker对未知格式返回通过;date 与其他格式的依赖要求不同。 - RFC 3339:完整日期及月份范围 (新标签页)↗
full-date 形式、月份天数与闰年约束,为合法和非法日期提供独立预期。
- jsonschema 4.26.0:错误对象的位置与原因 (新标签页)↗
区分
ValidationError和SchemaError,读取absolute_path、absolute_schema_path与失败关键字。