Skip to content
Guides

8 JSON Schema enum and const Tests Before Release

·10 min read
8 JSON Schema enum and const Tests Before Release - Guides guide from JSON Prettifier

Quick answer: Test enum and const across eight boundaries: the allowed set, missing fields, type restrictions, numeric equality, composite values, exact string spelling, array-item coverage, and contract changes. These checks use Draft 2020-12 and distinguish accepting a JSON value from filling a default, coercing a type, or authorizing a business operation.

An API accepts "queued" but rejects "Queued". Another accepts numeric 1.0 for a constant of 1 but rejects true. Those outcomes are understandable once the contract tests values and types explicitly instead of relying on a dropdown or a language’s equality operator.

The JSON Schema introduction provides broader context. Wikipedia’s enumerated-type overview explains the programming concept, while JSON Schema’s keywords can also restrict values beyond the named scalar constants common in programming-language enums.

1. Does the allowed set accept every intended value and reject a nearby outsider?

Test each intended enum member and at least one plausible value outside the set. Use const when the contract permits exactly one value, and verify that an unrelated value fails. Neither keyword selects a default or rewrites input, so keep acceptance assertions separate from any application code that maps labels or supplies missing values.

{"$schema":"https://json-schema.org/draft/2020-12/schema","enum":["queued","running","done"]}

All three listed states pass; "pending" fails. For {"const":"v2"}, only the string v2 passes. The official enum reference and constant-value reference define these restrictions. The useful release artifact is the expected set of outcomes, not merely a schema that successfully loads.

Confirm the vocabulary with the API owner before freezing it. A plausible status name is not evidence that the service supports it. Keep fixtures tied to the published state names and use a deliberately unsupported value to prove the boundary. If names are generated from application code, run these fixtures against the generated schema rather than only the source list.

A constant version marker does not migrate a document into that version. A request saying v2 still needs the complete v2 shape and semantics checked. Use a discriminating constant as one part of that contract, not as proof that every neighboring field has been interpreted correctly.

2. Does a constrained property still need an explicit presence requirement?

Put required on the enclosing object when an enumerated or constant-valued property must exist. A properties subschema only checks the named field when it is present. Test omission and explicit null independently, then compare them with accepted and unrecognized values so a missing discriminator cannot pass simply because its value constraint was never applied.

{
  "type":"object",
  "properties":{"kind":{"const":"email"}},
  "required":["kind"]
}

An object with kind equal to email passes. An empty object fails, as does an explicit null kind. Removing required makes the empty object pass, but does not make null equal to email. Preserve that deliberately weakened-schema fixture as a diagnostic for generators that accidentally omit required fields.

This matters particularly in conditional branches. If a branch recognizes kind through properties alone, a missing kind can satisfy a condition you intended only for one category. The conditional validation tests cover that broader interaction; this check establishes the smaller presence boundary first.

Also retain the enclosing object type. Object-specific keywords do not universally reject other top-level JSON types on their own. A null request body should fail because the endpoint requires an object, rather than being mistaken for an object whose discriminator was simply omitted. Presence, type, and fixed value are three assertions with separate useful negative fixtures.

3. Do type constraints intersect the allowed values rather than override them?

When type and enum appear together, an instance must satisfy both. Test every intended type, a value listed under the wrong type, and an unlisted value of the right type. Mixed enum entries do not override a narrower type declaration, and adding null to the allowed list cannot make it pass a string-only type constraint.

{"type":"string","enum":["auto",null,42]}

Only auto passes this example. Null and numeric 42 appear in the enumeration but fail the string requirement. Without the type restriction, those listed values pass. Neither schema accepts the string "42" merely because the numeric value 42 is listed. This is a useful distinction when clients serialize form controls as text.

The validation specification defines the relevant keywords independently. Treat a redundant or contradictory declaration as a review question even when it is structurally legal. A schema can be valid while admitting fewer useful instances than its author intended.

If null means an explicit off state, make both the enum and type policy agree. Do not use an implementation-specific nullable flag without confirming the chosen schema dialect or framework semantics. Test the raw JSON request through the actual adapter as well, because a coercion layer can turn a rejected string into an accepted number before validation sees it.

4. Does equality distinguish booleans from numbers while accepting equal numeric spellings?

Test booleans, numbers, and numeric strings independently. Under JSON Schema’s data model, true is not the number one, while numeric values written as 1 and 1.0 are equal. Avoid using a host-language membership expression as a substitute for schema evaluation, because its equality and coercion rules can produce a different allowed set.

{"enum":[1]}

Numeric 1 and 1.0 pass. True, false, and the string one written as "1" fail. In Python, a casual equality comparison can equate True with 1, so a hand-written list-membership check is not a reliable oracle for this schema. Let a compatible validator evaluate the fixture and record the result explicitly.

The core specification’s equality rules define the model. The numeric validation tests explain the related boundary between numeric value, token spelling, and runtime representation. An enum is not a lexical rule requiring a particular decimal spelling.

Large numeric values add another concern: a parser can lose information before the equality comparison runs. If exact large identifiers matter, test the decoder and the contract representation together. Passing a small numeric enum fixture cannot establish that a different runtime preserves every large identifier the API will encounter.

5. Does a composite allowed value require complete equality rather than a partial match?

An object or array in enum represents a complete allowed JSON value. Test reordered object members, an additional member, a missing member, and reordered array elements. Object order does not change equality, while array position does; neither enum nor const should be interpreted as a partial object template that permits unspecified neighboring fields.

{"const":{"enabled":true,"limits":[1,2]}}

The same object with limits written before enabled passes. Adding an extra label fails, omitting enabled fails, and changing limits to [2,1] fails. These fixtures establish complete-value equality. If your intent is merely to require an enabled field while permitting other fields, use an object schema with properties and the appropriate closure policy instead.

Use JSON Compare to inspect sanitized composite fixtures when a rejection is surprising. It helps reveal structural changes, but does not execute this schema. Its comparison settings are a separate policy, so do not assume an option that ignores array order is suitable evidence for enum or const acceptance.

Composite constants can be appropriate for a small protocol sentinel, but large frozen objects are difficult to evolve. A changed member can invalidate the whole constant. Prefer ordinary structural schemas when each member needs independent constraints, and reserve complete-value restrictions for situations where equality of the entire instance is the actual business requirement.

6. Are exact string spellings kept separate from normalization and labels?

String enum values are exact values, so test case changes, leading or trailing spaces, and visually similar spellings. A schema does not trim or case-fold the instance to match an allowed label. If normalization is part of the API policy, test it as an explicit transformation and validate the normalized result without disguising what the caller originally submitted.

For a state of queued, the strings "Queued", "queued ", and " queued" fail the enumeration in check one. A user interface can display a friendly label while sending the stable wire value. Translating that label should not silently change the API’s stored state or broaden the validator’s accepted spelling.

Keep Unicode cases relevant to your domain in the fixture set. Two strings that look alike on screen are not automatically the same sequence of characters. The Unicode comparison tests explain normalization boundaries in more detail. Do not introduce automatic normalization merely because one unusual input failed.

When debugging, retain escaped fixture text and its parsed string value. Copying a word from a screenshot can hide a trailing space or a different character. Give the failing fixture a descriptive name so the next maintainer can reproduce the boundary without guessing which invisible difference the test was meant to preserve.

7. Does the enumeration apply to every intended array element?

Use items with an enum subschema when each array element must belong to the allowed set. Test an invalid first element, an invalid later element, an empty array, and duplicates. Element membership does not imply a nonempty array or unique values, so add those independent constraints only when the API contract actually requires them.

{"type":"array","items":{"enum":["read","write"]}}

The arrays ["read","write"], [], and ["read","read"] pass this schema. ["read","admin"] fails, proving that validation reaches the second element. An enum placed on the entire array would instead compare the whole array against complete allowed values, which expresses a different contract.

If at least one permission is required, add minItems. If duplicate complete values are forbidden, add uniqueItems and keep that fixture separate from membership. The uniqueItems tests explain why value uniqueness and business identity are different questions.

A permission name being structurally allowed does not authorize the caller to grant it. Keep authorization independent of the list vocabulary, and test that an accepted list cannot be used to bypass resource or role checks. The schema establishes what the request can look like; application policy establishes which permitted action this caller may perform.

8. Do enum changes preserve the intended behavior across producers and consumers?

Run the fixture matrix against old and new contracts before changing an allowed set. A producer accepting a new state does not mean an older consumer can handle that state in responses. Test removal, addition, unknown-value handling, and generated-client behavior separately, then report compatibility for the actual direction and version boundary being changed.

This harness uses python-jsonschema’s documented validator interface. Record the dependency version with the test environment and keep expected outcomes in source control. The small numeric case deliberately includes a boolean and a string so a host-language equality shortcut cannot accidentally replace schema semantics.

import json
from copy import deepcopy
from jsonschema import Draft202012Validator

schema = {"enum": [1]}
Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)
for wire, expected in [('1', True), ('1.0', True), ('true', False),
                       ('false', False), ('"1"', False), ('null', False)]:
    value = json.loads(wire)
    before = deepcopy(value)
    assert validator.is_valid(value) is expected
    assert value == before

Repeat the same pattern for string states, composite values, required discriminators, and array items. Keep deliberate invalid-schema checks alongside instance fixtures when generating allowed sets, and decide what an empty generated set means before deployment. It should trigger a reviewed contract decision rather than silently removing the constraint.

Finally, exercise the endpoint and a representative consumer. Confirm that rejected values never execute the operation and that accepted values arrive without undocumented conversion. For a newly added response state, test the older consumer’s actual fallback or failure path. Compatibility is an observable interaction between versions, not a property established merely by appending one more string to a schema file.

Frequently Asked Questions

What is the difference between enum and const in JSON Schema?
enum restricts an instance to one of the values in its array, while const restricts it to one specified value. Both compare JSON values rather than generating defaults or converting input. Use const for a fixed discriminator or version marker, and enum for a deliberate allowed set whose accepted and rejected boundaries are tested.
Does enum make an object property required?
No. An enum inside properties constrains a property when it exists, but does not require that property to appear. Use required on the enclosing object when omission must fail. Test an empty object, an explicit null, an accepted value, and an unrecognized value separately because presence and allowed-value membership are independent assertions.
Are true and 1 equal for enum validation?
No. JSON Schema equality distinguishes booleans from numbers, so true does not satisfy enum containing only the number 1. Numeric values written as 1 and 1.0 are equal under the data model, however. Test the real validator rather than copying host-language equality rules, which can treat booleans and numbers differently from JSON Schema.
Can enum contain objects or arrays?
Yes. An allowed enum value can be an object, array, or another JSON value. The candidate must equal an entire allowed value; this is not partial object matching. Object member order does not affect equality, while array element order does. Test extra members, missing members, and reordered array elements before using composite allowed values.
Can JSON Compare replace enum or const validation?
JSON Compare helps inspect structural differences between sanitized JSON fixtures, but it does not execute an enum or const schema. Its comparison settings also express a separate policy. Preserve expected acceptance results in automated validator tests, and use the comparison tool to investigate which values, members, or array positions changed during debugging.

Working with JSON right now?

Paste it into JSON Prettifier to format, validate, or minify it. Everything runs in your browser.

Open JSON Prettifier