json-standards
Skill1 starsChanged 8 days agoMITView on GitHub
Standards for designing JSON Schemas (Draft 2020-12) and JSON payloads: dialect and $id declaration, modular $defs/$ref structure, composition (allOf/oneOf/anyOf, unevaluatedProperties, discriminator), versioning and backward compatibility, naming, and validator performance (Ajv compile-once). Use whenever writing, reviewing, or refactoring a JSON Schema, JSON API payload, or validation code. Trigger on: "JSON Schema", "$schema", "$ref", "$defs", "additionalProperties", "unevaluatedProperties", "oneOf", "validate JSON", "Ajv", "schema versioning", "API payload shape", "OpenAPI 3.1 schema", "discriminator", or any task producing or reviewing a .schema.json file or JSON contract between services.
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Every schema document you produce MUST start with:
- Very specific: line 49
Use readOnly/writeOnly for API semantics (a readOnly field must not be accepted in a PUT/POST body) and deprecated: true for fields slated …
- Distinctive: line 56
Treat schemas like code: factor reusable units instead of duplicating.
- Very specific: line 60
Rule of two: if a subschema is used once, inline it.
- Distinctive: line 228
Producer-closed / consumer-open split: when a strict schema registry (e.g.
- Distinctive: line 233
If consumers may lag several versions, require transitive compatibility and keep the full migration chain.
- Distinctive: line 336
Before presenting a schema you wrote or approving one you reviewed, verify each item.
- Distinctive: line 337
If any check fails, fix it before delivery.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
JSON Schema & JSON Payload Standards
Adhere strictly to these standards when generating, reviewing, or refactoring JSON Schemas and JSON payloads. They target Draft 2020-12.
1. Foundations — every schema, no exceptions
Every schema document you produce MUST start with:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user/v1",
"title": "User",
"description": "A registered user of the platform.",
"type": "object"
}Rules:
- Always declare
$schemawith the 2020-12 dialect URI. Omitting it lets each validator pick its own default (Ajv 6 → Draft-07, Ajv 82020build → 2020-12), which makes validation non-deterministic and silently ignores newer keywords likeprefixItemsunder an older dialect. - Always give each schema file an absolute, fragment-free
$id. In 2020-12,$idmust not contain a fragment.$idis an identifier, not a URL to fetch — validators resolve references from their internal schema registry, not the network. - Always add
title(short) anddescription(longer) at the root and on any non-obvious property. Addexamples(array — the 2020-12 keyword, not singularexample) illustrating valid instances. defaultis documentation only — it does not fill in missing values during validation. Never rely on it for behavior.- Use
readOnly/writeOnlyfor API semantics (areadOnlyfield must not be accepted in a PUT/POST body) anddeprecated: truefor fields slated for removal.
2. Structure and modularity
Treat schemas like code: factor reusable units instead of duplicating.
Rules:
- Rule of two: if a subschema is used once, inline it. The moment it is used twice,
move it to
$defsand reference it with$ref. Split into separate files (each with its own$id) when a file becomes hard to read or a type is shared across schemas. - Keep nesting shallow — 2–3 object levels. Flatten deeper structures.
- Use
$defs, not the legacydefinitionskeyword, for local reusable subschemas. - Prefer arrays of homogeneous items over dynamically-keyed objects. Objects whose key names vary between responses are an anti-pattern — unparseable in strictly-typed consumers.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order/v1",
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/Address" },
"shippingAddress": { "$ref": "#/$defs/Address" }
},
"$defs": {
"Address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
}
}$anchordefines a plain-name fragment safe to add without changing resolution scope. Referencing another file's$anchoris an anti-pattern — promote shared subschemas to standalone resources with their own$idinstead.- Bundle at build time, never resolve remote
$refs at runtime. Use the 2020-12 Compound Schema Document pattern (embedded resources keep their own$idand$schema, reference URIs unchanged) via tools like@hyperjump/json-schema-bundleor thejsonschemaCLIbundlecommand. - Avoid
$dynamicRef/$dynamicAnchorunless you fully understand their behavior — the spec authors themselves advise against casual use. - Circular
$refs are valid for genuinely recursive types (trees); be aware they can exponentially increase validation time (§9).
3. Validation keywords
typeis the foundation of every subschema. Express nullability as a type array:"type": ["string", "null"]— never OpenAPI 3.0's removednullable: true.enum/constfor closed value sets. Do not combineenumwithtype— the enum values already imply their types; the combination is redundant and an anti-pattern.patternuses ECMA-262 regex. Always anchor patterns (^...$) and avoid constructs prone to catastrophic backtracking (ReDoS) on untrusted input; bound input withmaxLength.formatis an annotation, NOT an assertion, by default in 2020-12. A schema with"format": "email"alone validates"not-an-email"successfully on a compliant validator. When enforcement matters: pairformatwith apattern, or enable the validator's opt-in format assertion (e.g.ajv-formats), or validate in the application layer. Never invent customformatvalues — introduce a distinct keyword or usepattern.- Numeric:
minimum/maximum/exclusiveMinimum/exclusiveMaximumtake numbers in 2020-12 (not the old boolean form);multipleOffor granularity. JSON Schema treats1and1.0as equal. - String:
minLength/maxLength— setmaxLengthon any user-supplied string. - Array:
prefixItemsfor tuple positions +itemsfor the rest (this replaced the old array-formitems/additionalItems);minItems/maxItems,uniqueItems,contains/minContains/maxContains. - Object:
properties,patternProperties,propertyNames,minProperties/maxProperties, and the extension-control keywords covered in §5.
4. Required vs optional, null vs absent
- All properties are optional by default;
requiredis a separate array of names. Mark only genuinely mandatory fields — over-constraining kills evolvability (§6). nulland absent are semantically different. Missing key = "does not apply";"field": null= "exists, has no value". Make a field both optional and nullable only when the API genuinely distinguishes them (e.g. PATCH wherenullmeans "clear" and omission means "leave unchanged"). If you cannot describe a scenario where{}and{"field": null}behave differently, the field is optional but not nullable.dependentRequiredexpresses "if A is present, B is required":
{ "dependentRequired": { "creditCard": ["billingAddress"] } }Dependencies are one-directional — declare both directions if both are needed.
if/then/elsefor conditional subschemas;dependentSchemasto apply a whole subschema when a property is present.
5. Composition — the rules that prevent broken schemas
allOf= intersection (all subschemas must pass). It is not OO inheritance: each subschema validates independently, soadditionalPropertiesin one branch cannot see properties defined in another.oneOf= exactly one branch. Correct for union types, but every branch must be evaluated to prove exactly-one — use a discriminator for tagged unions (§9).anyOf= at least one. Faster to validate thanoneOf(short-circuits), but has no established code-generation convention — avoid unless the semantics genuinely require "one or more".not— use sparingly; code generators cannot interpret it.- Watch for logically impossible combinations (e.g.
"type": "string"with ananyOfof numericconsts) — they never validate and fail silently.
The additionalProperties: false trap — never use it in composed schemas. It cannot
"see inside" $ref, allOf, or oneOf subschemas, so it rejects the very properties
those branches define, and it makes the schema impossible to extend. This is
spec-mandated behavior in every compliant validator, not a bug. The fixes:
- Extension:
allOf+unevaluatedProperties: falseat the top level — it can see through$refand insideallOf/anyOf/oneOf/if/then/else:
{
"$id": "https://example.com/schemas/employee/v1",
"allOf": [{ "$ref": "https://example.com/schemas/person/v1" }],
"properties": { "employeeId": { "type": "string" } },
"required": ["employeeId"],
"unevaluatedProperties": false
}- Tagged unions:
oneOf+ a discriminator property that isrequiredwith aconstin each branch (validators like Ajv withdiscriminator: truethen validate only the matching branch):
{
"oneOf": [
{ "properties": { "kind": { "const": "card" }, "cardNumber": { "type": "string" } },
"required": ["kind", "cardNumber"] },
{ "properties": { "kind": { "const": "sepa" }, "iban": { "type": "string" } },
"required": ["kind", "iban"] }
]
}- Or simply leave the model open and have consumers ignore unknown properties — the spec-committee-recommended default for anything that will be extended.
6. Versioning and compatibility
Two independent axes — never conflate them:
- Dialect version — the
$schemakeyword (2020-12). - Content version — your document's own version, carried in the data.
Rules:
- Embed a
schemaVersionfield in instance data; validate against the schema for that exact version, then migrate to a single latest in-memory representation. - Version the
$id(andtitle):https://example.com/schemas/user/v2. - Additive changes only within a major version. Adding optional fields or relaxing constraints is backward-compatible. Removing, renaming, or retyping a field is breaking — once a field ships, its name and type are frozen until the next major version, and a removed name must never be reused within one.
- Breaking changes use three-phase deprecation: (1) add the new field alongside the
old and dual-write, (2) migrate consumers, (3) remove the old field. Mark the old
field
"deprecated": truein the interim rather than deleting it. - Maintain a monotonic migration registry with
up(N→N+1) anddown(N+1→N) transforms; never delete old entries. Test every transition with fixture files and round-trip/idempotency checks. - Producer-closed / consumer-open split when a strict schema registry (e.g. Confluent) checks compatibility: producers validate against a closed content model, consumers against an open one (ignore unknown fields). This keeps adding/removing optional fields fully compatible — registries treat even additions to an open model as incompatible otherwise.
- If consumers may lag several versions, require transitive compatibility and keep the full migration chain.
7. Naming conventions
Consistency within an API is paramount — mixing conventions in one payload is the worst outcome.
| Concern | Rule | Example |
| Key case | camelCase for JS-centric/public APIs; snake_case for Python/DB-aligned APIs. Pick one per API, enforce with a linter | firstName / first_name |
| First character | Letter, _, or $ (Google JSON Style Guide) | — |
| Kebab-case | Never — forces bracket notation in most languages | first-name |
| Reserved words | Avoid language keywords as keys | classtype |
| Arrays | Plural nouns | orders, lineItems |
| Booleans | Positive framing | isActive, not isNotDeleted |
| Timestamps | ISO 8601 UTC | "2026-06-07T10:30:00Z" |
| Domain fields | One canonical name per concept, everywhere | always userId, never uid/user_id mixed |
- Consistent types across responses and versions — never
42in one response and"42"in another. - Never leak database internals: sequential IDs as identity, table/column names, soft-delete flags.
- Enforce naming and structure with a linter (Spectral, vacuum) in CI.
8. Testing and tooling
- Every schema gets tests against both valid AND invalid fixtures in CI. A schema
only tested with valid data hides accept-everything bugs (e.g. an unasserted
format, a misplacedrequired). - Validators: Ajv (JS/TS — use the
ajv/dist/2020build for 2020-12, addajv-formatsfor format enforcement),@hyperjump/json-schema(JS, bundling), Pythonjsonschema,json-everything(.NET). One shared validator instance per process. - Linting/governance: Spectral, vacuum,
jsonschemaCLI (format/lint/test/bundle). - Conversion: AlterSchema (between drafts),
openapi-schema-to-json-schema. - Codegen: quicktype, openapi-generator, TypeBox. TypeScript types are compile-time only — pair them with runtime validation (JSON Schema or Zod); use both, not either.
- OpenAPI 3.1 is a superset of JSON Schema 2020-12 — schemas are portable. When
migrating from 3.0:
nullable: true→"type": [..., "null"], booleanexclusiveMinimum/Maximum→ numeric, single-valueenum→const, singularexample→examples.
9. Performance
- Compile once, validate many. Ajv compiles each schema into an optimized JS
function; calling
ajv.compile()per request is the single most common performance mistake. Compile at startup (or precompile to standalone code for hot paths) and reuse the compiled function:
// startup
import Ajv2020 from "ajv/dist/2020";
const ajv = new Ajv2020();
const validateUser = ajv.compile(userSchema); // once
// request path
if (!validateUser(payload)) handle(validateUser.errors); // reuse- Recursive schemas and deep
allOf/oneOf/anyOfchains can exponentially increase processing time. Flatten where possible. - Replace
oneOfwith a discriminator on hot paths — only the matching branch is validated, and error messages improve. unevaluatedPropertiesis a dynamic keyword processed after all static keywords — it fixes composition (§5) but is not free; don't sprinkle it where a plain open model suffices.- Ajv knobs:
loopRequired/loopEnumdefault to 200 (above that, slower loop-based validation);ajv-formatsis opt-in because bundling all format regexes is costly. - Guard against ReDoS in
pattern: anchor, keep regexes simple, bound length.
Anti-pattern quick reference
| Anti-pattern | Instead |
Omitting $schema | Always declare the 2020-12 dialect URI |
$id with a fragment, or no $id | Absolute, fragment-free $id per file |
additionalProperties: false in/under composition | unevaluatedProperties: false, discriminator, or open model |
Relying on format for validation | Pair with pattern, enable format assertion, or validate in app layer |
enum combined with type | enum alone |
Custom format values | New keyword or pattern |
nullable: true | "type": ["...", "null"] |
Everything required / overly strict v1 | Only truly mandatory fields; leave room to evolve |
| Retyping or renaming a shipped field | Additive change + three-phase deprecation |
| Dynamically-keyed objects for lists | Arrays of homogeneous items |
Referencing another file's $anchor | Standalone resource with own $id |
Runtime remote $ref resolution | Build-time bundling |
ajv.compile() per request | Compile once at startup, reuse |
| Only-valid-data tests | Valid and invalid fixtures in CI |
| Mixed key casing / mixed types across responses | One convention, one type, linted |
Pre-flight checklist — run before delivering any schema
Before presenting a schema you wrote or approving one you reviewed, verify each item. If any check fails, fix it before delivery.
$schemadeclares Draft 2020-12;$idis absolute and fragment-free.- Root has
titleanddescription; non-obvious properties documented;examplespresent. requiredlists only genuinely mandatory fields; every nullable field has a justifiednull-vs-absent distinction.- No
additionalProperties: falseanywhere near$ref/allOf/oneOf/anyOf— usedunevaluatedProperties: false, a discriminator, or an open model instead. - No
formatrelied on for enforcement without a pairedpatternor explicit format-assertion setup; allpatterns anchored; user-supplied strings bounded withmaxLength. - No
enum+typecombos, nonullable: true, no customformatvalues. - Subschemas used more than once live in
$defs; nesting ≤ 3 levels; no dynamically-keyed objects. - Naming: one case convention throughout, plural arrays, positive booleans, ISO 8601 UTC timestamps, canonical domain field names.
- Versioned
$idwhen the schema is a published contract; changes since the previous version are additive (or follow three-phase deprecation). - Validator guidance included where relevant: compile-once pattern,
ajv-formatsif format enforcement is expected, valid + invalid test fixtures.
In this repository91 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- goSkill
- pythonSkill
- rabbitmqSkill
- rSkill
- rustSkill
- code-standardsSkill
- software-architectureSkill
- swiftSkill
- typescript-code-standardsSkill
- architecture-documentation-and-decision-recordsSkill
- business-requirements-standardsSkill
- developer-handoff-standardsSkill
- development-planning-standardsSkill
- document-writer-reviewerSkill
- feature-planning-standardsSkill
- functional-specification-standardsSkill
- non-functional-requirements-standardsSkill
- operations-standardsSkill
- product-requirements-standardsSkill
- documentationSkill
- solution-architecture-standardsSkill
- technical-requirements-standardsSkill
- documenting-codeSkill
- github-circleci-standardsSkill
- graphifySkill
- puml-diagramsSkill
- software-architecture-progressionSkill
- testingSkill
- software-test-specificationSkill
- zeno-councilSkill
- zeno-debugSkill
- zeno-insightSkill
- zenoSkill
- zeno-stackSkill
- zeno-accessibilitySkill
- zeno-angular-developerSkill
- zeno-api-designSkill
- zeno-browser-qaSkill
- zeno-build-resolverSkill
- zeno-codebase-onboardingSkill
- zeno-code-tourSkill
- zeno-comment-analysisSkill
- zeno-config-gcSkill
- zeno-context-budgetSkill
- zeno-context-compactionSkill
- zeno-cost-reportSkill
- zeno-database-migrationsSkill
- zeno-deep-researchSkill
- zeno-deployment-patternsSkill
- zeno-design-systemSkill
- zeno-docker-patternsSkill
- zeno-e2e-testingSkill
- zeno-error-handlingSkill
- zeno-find-skillsSkill
- zeno-frontend-designSkill
- zeno-github-opsSkill
- zeno-git-workflowSkill
- zeno-harness-optimizerSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

