Define a Reusable JSON Configuration with Explicit Validation Rules

Define explicit JSON configuration rules for required fields, unknown properties and dependencies while keeping schema validation separate from applying defaults.

Choose a dialect; create known valid/unknown-property/dependent-missing fixtures; keep validation and application default insertion as different operations.

Substantively revised October 21, 2026. Author interpretation, documented cases and hypothetical examples are distinguished below.

A configuration file can look carefully documented while its validator accepts the wrong input. The author lists every intended property, writes a default beside an optional value and assumes the contract is complete. Then a misspelled property passes, a necessary companion field disappears, or an extension fails because a supposedly reusable base schema rejects the added property.

These are different design failures. They cannot be repaired by adding a longer description to the configuration file. The schema author needs to state which inputs are accepted, which relationships are required and which operations belong to the application after validation. A useful reusable configuration starts with those decisions rather than a catalog of plausible field names.

Direct answer

Choose a JSON Schema dialect and a validator that supports it. Declare the object type, describe property constraints, list mandatory properties explicitly and decide how unknown properties are handled. Express property-presence dependencies deliberately instead of assuming that related fields require one another. Keep default annotations separate from any application step that inserts values. Then compare known acceptable inputs with deliberately unacceptable variants using the actual chosen validator. The official JSON Schema documentation distinguishes each of these mechanisms. [S1] [S2] [S3] [S4]

This article uses Draft 2020-12 as a bounded reference for its illustrative contract. It does not assert that every library supports that dialect or that a schema-valid configuration is safe to execute. The worked inputs are invented design examples with expected outcomes explained from the stated rules, not observations from a running production service.

Key takeaways

Declaring a property and requiring its presence are separate decisions. A beautifully described field can still be optional under the schema. Write down the acceptable absence cases before deciding which properties belong in a required list.

An unknown-property policy is part of the public interface. Rejecting an unrecognized key can catch a typo, but it can also reject a deliberate extension. Do not close a base object and later assume that a composition keyword works like textual merging of field declarations.

A default annotation explains an intended value; validation alone does not insert it into the input. If the application normalizes omitted fields, make that a named, testable operation. Review both the received input and the configuration the application ultimately uses instead of silently treating them as the same object.

Definitions and relationships

A schema describes constraints applied to an instance, the JSON value being evaluated. A dialect identifies the vocabulary and semantics the schema intends to use. A validator is an implementation that evaluates an instance under supported rules. These are related pieces of a contract, not interchangeable names for the application consuming the configuration.

For this article, an acceptance fixture is a small example paired with an expected validation outcome. A normalization step is application behavior that deliberately transforms received data, such as supplying an omitted operational value. A configuration contract includes the schema, its declared dialect, its fixtures and the documented relationship to that application behavior.

The distinction matters when two systems use the same file. One may validate the received object and leave it untouched. Another may validate and then insert application defaults. Unless the sequence is explicit, both maintainers can say they use the schema while the program ultimately receives different objects.

The problem with documenting only the happy path

A sample configuration shows one object the author expects people to use. It does not disclose which parts are indispensable. Readers cannot reliably infer whether an omitted field is allowed, whether an unfamiliar key is ignored or rejected, or whether two optional-looking fields have a required relationship.

That ambiguity becomes a maintenance problem when the configuration is reused. A colleague copies the sample and misspells a property. A later version adds an optional feature. Another tool emits a value with a different JSON type. Without explicit negative examples, a passing result may reflect either the intended contract or a rule that was never written.

Begin with the smallest unacceptable change to a known acceptable object. Delete a required field. Replace a recognized key with a misspelling. Supply a triggering property without its required companion. Each variation asks one question of the contract. A large example containing several simultaneous errors makes the result harder to interpret.

Root cause: declarations are read as intentions

Humans read names and explanations cooperatively. If a property is called outputDirectory, a reader may infer that every export needs it. If the schema includes default, a reader may infer that the application will receive a value. The validator operates on the rules implemented for the declared dialect, not on the author's unstated expectations.

The schema author's job is therefore to translate intentions into explicit acceptance decisions. A property description can communicate meaning without enforcing presence. A dependent-property rule can enforce a relationship without requiring both properties in every object. An annotation can guide documentation without changing received data.

This translation is where reusable design becomes practical. The author must decide which variation the interface should tolerate and which variation it should reject. Reuse does not mean accepting every plausible object. It means maintaining a comprehensible contract whose extension rules are deliberate enough for another program or maintainer to rely on.

Framework: the Acceptance Boundary Grid

The Acceptance Boundary Grid is an author-proposed method for designing a small configuration contract. Its components are Dialect, Shape, Presence, Relationships and Transformation. It is not an official JSON Schema keyword, a formal proof system or a validated security technique.

Dialect records the intended rule language and the implementation used to evaluate it. The schema's declaration and the validator's capabilities need to agree. A fixture's outcome is meaningful only when the maintainer knows which rules were actually evaluated, rather than assuming a modern-looking identifier makes an older implementation compatible.

Shape states the allowed kinds of values and the policy for extra property names. Describe which properties can occur and what their values must look like. Decide whether a typo should fail immediately or whether an extension area intentionally permits additional names. An unexplained mixture is harder to maintain than either coherent policy.

Presence separates a field's existence from its value constraints. Record which properties must always occur, which can be absent and which optional fields have a legitimate absent state. A required field can still have an inadequate value constraint; a tightly constrained optional field can still be absent. Both questions deserve their own fixtures.

Relationships states dependencies among fields. Write the rule in plain language with its direction visible: if this property is present, that companion must be present. Then write the reverse case separately. This prevents the author from assuming that a one-way dependency expresses a two-way agreement.

Transformation identifies application changes to the received object. If a default is supplied, state which component supplies it and when. Keep that behavior distinguishable from evaluating the schema. A maintainer can then investigate whether a problem concerns input acceptance or a subsequent transformation rather than changing the wrong layer.

Apply the grid by pairing each component with a small positive and negative example where relevant. The resulting collection is a design conversation made executable in the chosen environment. It does not eliminate semantic review: a permitted string can still name the wrong real-world resource, and a structurally acceptable configuration can still request an inappropriate operation.

Three documented mechanisms and a dialect boundary

The official object reference distinguishes property definitions, required names and additional properties. It also explains that additionalProperties recognizes declarations in its own subschema rather than treating allOf as a field-merging operation. That distinction is central when a closed object is intended to become a reusable base for extensions. The author must design the closure and composition deliberately. [S1]

The conditional reference describes dependentRequired as a property-presence dependency. It explicitly notes that the dependency is not automatically bidirectional. Presence of one property can require another without making the reverse condition true. It also documents dialect history, so an author should not assume that the same keyword belongs to every older draft. [S2]

The annotations reference states that default does not fill missing instance values during validation. Tools serving other purposes may use annotations, but that possibility is different from a validation guarantee. A configuration consumer that inserts values needs a separately understood behavior, not an inference from a friendly schema label. [S3]

The dialect reference explains the root-level $schema declaration and warns that implementation support matters, especially when custom vocabulary is involved. This article therefore names its illustrative dialect and avoids inventing a custom enforcement keyword. A rule the implementation does not understand cannot be relied on simply because its name appears in a JSON document. [S4]

These are documented language mechanisms. They are not measurements of three software teams, and they do not establish how an unnamed validator behaves. The fictional contract below shows how a maintainer can make expectations inspectable before exercising them in the actual selected implementation.

Hypothetical case study: a small report builder

Imagine an invented command-line report builder with three configuration fields: title, exportPath and exportFormat. The team wants title in every object. If exportPath is present, exportFormat must also be present. An exportFormat without exportPath is allowed as a preference for a later operation. Those are illustrative product decisions, not universal recommendations for report tools.

The proposed contract uses the following complete schema as a compact illustration of those decisions: {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"title":{"type":"string","minLength":1},"exportPath":{"type":"string","minLength":1},"exportFormat":{"enum":["pdf","html"],"default":"pdf"}},"required":["title"],"dependentRequired":{"exportPath":["exportFormat"]},"additionalProperties":false}.

The first fixture is {"title":"Weekly review"}. Its expected result under this contract is acceptance. The absent export fields are deliberate. The default annotation does not mean the received object now contains exportFormat. If a later application step supplies pdf, that step should be described and checked on its own.

The second fixture is {"title":"Weekly review","exportPath":"preview/report.pdf","exportFormat":"pdf"}. Its expected result is acceptance under the stated structural rules. That result does not establish that the path is writable, that its destination is appropriate or that the application can generate a PDF. Those are operational questions outside this schema example.

The third fixture is {"title":"Weekly review","exportPath":"preview/report.pdf"}. Its expected result is rejection because the triggering property occurs without the required companion. Adding default to exportFormat does not answer that rejection. The schema author must either change the acceptance policy deliberately or preserve the explicit dependency.

The fourth fixture is {"title":"Weekly review","exportFormat":"html"}. Its expected result is acceptance. The dependency is one-way, and the invented product permits a stored format preference without an immediate export path. If the actual product requires the two fields together, the contract needs an explicit reverse condition and corresponding fixtures.

The fifth fixture is {"title":"Weekly review","exportFormatt":"pdf"}. Its expected result is rejection under the closed-name policy. The typo is a deliberate negative example. A property that the application will never read should not appear to work merely because the sample contained enough other information to finish a normal run.

Two more fixtures examine title: {} is expected to fail because the mandatory property is absent, while {"title":""} is expected to fail because its string is empty. A whitespace-only title illustrates a remaining limit: the stated minLength rule alone does not express a requirement for meaningful non-whitespace wording. The contract should not claim a condition it has not encoded.

These seven fictional fixtures distinguish several decisions using a deliberately small object. They are expected outcomes derived from the written contract, not a claim that a validator was executed for this article. Before adoption, run them with the actual implementation, record its version and investigate any disagreement instead of treating the example as production approval.

Failure example: the closed base that cannot grow

Suppose a later maintainer wants to reuse a base object for several kinds of report configuration. She places the closed base in allOf and declares an extra property in a different subschema. She expects the declarations to combine like two object literals in application code. The new object instead encounters the base's unknown-property restriction.

The failure concerns the meaning of composition, not an arbitrary reluctance to support reuse. A closed base has made a statement about accepted names within its own context. Adding another constraint elsewhere does not automatically rewrite that statement. The official object documentation describes this extension problem. [S1]

The design response should be explicit. Decide where closure belongs, choose a composition strategy supported by the declared dialect and write an extension fixture that must succeed alongside a misspelling fixture that must fail. This article does not prescribe a universal composition recipe for every nested contract; it identifies the acceptance decisions the maintainer must preserve.

Counterargument and tradeoff: rejecting unknown keys can prevent extension

A strict unknown-name policy is useful when a small configuration interface should catch spelling errors. It can be inappropriate when the design intentionally accommodates plugin-specific settings. The counterargument is legitimate: rejecting every unfamiliar field can make a shared contract brittle across independently evolving components.

The alternative is a deliberate extension boundary, not unexplained permissiveness. A maintainer might define a documented extension area whose rules differ from the core object. The actual schema still needs to express the chosen shape and the consumer needs to interpret it. Merely allowing any property everywhere does not define a plugin protocol.

The maintenance tradeoff is intelligibility. A restrictive interface is easier to explain but requires deliberate versioning when accepted names grow. An extensible interface offers flexibility but needs clear ownership of the additional meanings. Choose according to the real consumers and write fixtures that protect the intended boundary rather than reward whichever policy produces more passing files.

Common mistakes and their repairs

Making every described property required can reject legitimate small configurations. Begin with absence cases the product actually supports. Then distinguish unconditional presence from conditional relationships. The smaller contract should preserve genuine optionality rather than use one large required list as a substitute for product reasoning.

Adding an annotation to fix a rejected instance can hide the actual issue. Ask whether the application is supposed to transform received data or whether the input contract should require an explicit value. Those choices have different consequences for logs, debugging and consumers that use validation without transformation.

Using only one large valid fixture leaves the negative boundary unexamined. Change one relevant feature at a time so that a surprising outcome can be traced to a specific rule. Keep an expected result beside the fixture; a folder full of examples without acceptance expectations is still an ambiguous specification.

Calling a configuration safe because it is schema-valid confuses structural acceptance with operational authorization. A permitted path or name can still refer to the wrong resource. Design the runtime checks appropriate to the operation, and do not turn this small schema example into a security endorsement.

Action plan for a shared configuration interface

Write the intended consumer behavior before creating the full schema. Identify the smallest useful object, mandatory names, optional relationships and unknown-name policy. Choose the dialect and confirm that the intended validator supports the required vocabulary. Keep that implementation choice alongside the contract so a later dependency change has a visible point of comparison.

Create the schema and a short fixture set covering valid minimum input, a normal fuller input, a misspelled key, a missing required field and a one-way dependency contrast. Add value-boundary fixtures appropriate to the actual product. The seven examples here demonstrate a method; they are not sufficient coverage for an unrelated configuration language.

Run the fixtures in the actual environment and inspect the object before and after validation. If the chosen library offers optional mutation or default insertion, document that separately from the schema's language-level guarantees. The application's effective configuration should have a traceable relationship to the received input.

When extending the contract, preserve the old accepted cases that remain part of the supported interface and add the new positive and negative examples. Explain any intentional compatibility break. A reusable schema is maintained through visible acceptance decisions, not by hoping that another field declaration will be interpreted as the author intended.

FAQ

Does properties make a field mandatory?

No. A declaration of its value constraints is distinct from a requirement that the name occur. State presence requirements explicitly and include an absent-field fixture. The official object reference documents that separation. [S1]

Does required reject an empty string?

Presence alone does not express that value constraint. Decide which string contents are acceptable and encode the relevant rule. In the example, minLength distinguishes an empty string, while meaningful wording remains a separate product requirement.

Why declare a dialect?

It tells readers and compatible tooling which rule vocabulary the schema intends. The implementation still needs to support that vocabulary. The declaration is not evidence that every library evaluating the file has the necessary capabilities. [S4]

Does default insert a missing value?

Not through JSON Schema validation alone. The official annotations documentation makes this distinction. An application or library option that supplies values is separate behavior and should be described and exercised explicitly. [S3]

Is a dependency automatically two-way?

No. A dependentRequired rule has a direction. The hypothetical format-only object illustrates why the reverse case needs its own expectation. Add a reverse requirement only when the real product's acceptance policy calls for it. [S2]

Can an optional property trigger a requirement?

Yes, that is the role of the illustrated presence dependency. The triggering property need not occur in every object. When it does occur, the stated companion requirement applies under the chosen contract.

Will allOf merge my closed object declarations?

Do not treat it as textual object merging. The object reference explains why additionalProperties in a closed subschema can reject a property declared elsewhere. Design the extension boundary for the dialect and protect it with a positive extension fixture. [S1]

Should every unknown property fail?

That is an interface decision. A small fixed configuration may benefit from rejecting typos; a deliberate extension protocol may need another policy. Describe which consumer owns unfamiliar names rather than rely on general permissiveness to define their meaning.

Are the example fixtures measured results?

No. They are invented inputs with expected outcomes explained from the illustrative schema. Run them in the actual selected validator before using that contract. This article does not report a production execution or compatibility assessment.

Does a valid path mean an export will work?

No. A structurally accepted string does not establish filesystem access, appropriate destination or successful output generation. Those conditions belong to the actual application and its operating environment.

Does the schema establish user intent?

No. It evaluates a representation under specified constraints. It cannot independently determine why a person supplied a value or whether an application-generated value reflects an explicit choice. Avoid attaching those meanings to a passing result.

Should I use custom keywords for business rules?

Only with a deliberate implementation and interoperability plan. A keyword name alone does not supply enforcement. The dialect documentation discusses the support problem; this article keeps its example within the stated standard vocabulary. [S4]

What should I do with an unexpected pass?

Confirm the selected dialect, implementation capabilities and exact input before revising the schema. Then inspect whether the intended rule was actually expressed. Do not immediately weaken a negative fixture just to align the expectations with a convenient result.

How many fixtures are enough?

There is no universal number supplied here. Cover the actual acceptance decisions and their important interactions. Add cases when a new requirement or defect exposes an unexamined boundary rather than mechanically copy the seven illustrative inputs.

What belongs in the contract handoff?

Include the schema, declared dialect, implementation/version used, fixture expectations and any separate normalization behavior. Explain the scope of a passing result. A later maintainer should be able to reproduce acceptance decisions without assuming that annotations or application actions are part of validation itself.

Recommended books

John Ousterhout's A Philosophy of Software Design, second edition, provides background for reducing interface complexity and deciding which details a consumer should need to understand. Use it to examine the configuration boundary. It is not a specification of JSON Schema keywords or proof that this illustrative contract is complete.

William Kent's Data and Reality, third edition, with commentary by Steve Hoberman, offers background on the relationship between representations and their intended meanings. It helps question what a permitted name or value actually represents. It does not determine whether a particular validator implements the declared dialect.

The Pragmatic Programmer, 20th Anniversary Edition: your journey to mastery, by David Thomas and Andrew Hunt, provides broader context for explicit expectations and deliberate use of tools. Its relevance is the habit of maintaining understandable examples. The operational semantics discussed here rely on the official JSON Schema documentation.

Further exploration

Trace one actual configuration from the received file to the object used by the application. Identify parsing, validation, normalization and runtime checks as separate operations. Observe which component supplies each change and which failures it can report. That trace can expose a missing control more effectively than expanding the schema's descriptive text.

Then examine an intended extension with a second consumer. Ask which older inputs remain supported and where new names can appear. A reusable contract improves when compatibility is an explicit decision with representative evidence, rather than an optimistic reading of a property catalog that different programs interpret differently.

Primary evidence sources

Related concepts: systems thinking, wealth structure, and the survival loop. Read the editorial policy and citation guide.