Quick answer: Test JSON Schema content keywords across five boundaries: outer annotation behavior, bounded decoding, decoded text parsing, inner-schema validation, and application policy. These examples target Draft 2020-12. An outer document can satisfy its schema while its embedded string is malformed, so a successful validator result must not be mistaken for completed content processing.
A client sends Base64 containing a small JSON document. The outer validator returns true, but the decoded document is missing a required field. That is not necessarily a validator bug: the application may never have run the separate content-processing stage its contract needs.
The JSON Schema overview introduces structural assertions. The binary-encoding tests cover encoding contracts more broadly. Here the focus is the content vocabulary and its processing boundary. Wikipedia’s media-type background explains content labels; a label alone is not proof that a parser can safely process the bytes.
1. Does ordinary outer validation leave content processing as a separate operation?
Test the enclosing schema with malformed encoded strings and confirm which ordinary assertions actually apply. Draft 2020-12 content keywords are annotations rather than automatic content assertions, so outer validity does not establish successful decoding or embedded validation. Preserve separate results for the enclosing instance and its embedded document instead of collapsing them into one unexplained boolean.
{
"$schema":"https://json-schema.org/draft/2020-12/schema",
"type":"object",
"properties":{
"data":{
"type":"string",
"maxLength":512,
"contentEncoding":"base64",
"contentMediaType":"application/json",
"contentSchema":{
"type":"object",
"properties":{"name":{"type":"string"}},
"required":["name"],
"additionalProperties":false
}
}
},
"required":["data"],
"additionalProperties":false
}
A short data string such as "not base64!" passes ordinary outer validation in the checked python-jsonschema configuration. An absent data property or a numeric data value fails. These fixtures distinguish real outer assertions from descriptive content metadata. The Draft 2020-12 content vocabulary specifies this separation and prohibits automatic decoding by default.
Include a nonstring data fixture and an oversized string alongside the malformed-content fixture. Those cases should fail outer assertions even when content processing is disabled. This contrast prevents the opposite misunderstanding: annotation-only content handling does not make every keyword in the containing schema optional. Required fields, types, length limits, and object closure still define the container your application accepts. Name each fixture after the boundary it proves.
Some implementations offer additional content-processing features. Record whether those features are enabled, how they return embedded results, and which media types they support. Do not assume another validator, command-line tool, or queue consumer performs the same work merely because it understands the schema file.
The official non-JSON-data guide shows how the keywords describe string content. Keep an outer-valid, content-invalid fixture in the release suite: it prevents a future maintainer from removing the explicit processing stage after seeing a successful schema result. The acceptance policy for your endpoint can still reject that request based on its separate content result.
2. Does the explicit decoder enforce the selected encoding and size policy?
Choose the decoder through trusted application configuration, then test malformed characters, the selected alphabet, and encoded and decoded size limits independently. A contentEncoding annotation does not configure the decoder for you. Bound work before decoding where possible, and preserve a separate failure category so an encoding error cannot be confused with an inner-schema violation.
import base64
import binascii
raw = base64.b64decode('eyJuYW1lIjoiQWRhIn0=', validate=True)
assert raw == b'{"name":"Ada"}'
try:
base64.b64decode('not base64!', validate=True)
except binascii.Error:
pass
else:
raise AssertionError("Malformed Base64 unexpectedly decoded")
Python’s Base64 documentation explains the decoder’s validation option. Here it rejects characters outside the selected Base64 alphabet rather than silently discarding them. Do not describe that option as a complete canonical-encoding policy: padding, alternate alphabets, and equivalent encodings need their own explicit decisions when they matter to the protocol.
The outer schema’s 512-character cap and the later 256-byte decoded cap are small illustrative fixture limits. Choose real limits from your endpoint’s requirements, and test exactly at and beyond each boundary. An encoded-length assertion cannot replace the HTTP body limit, because the complete request must already be received and parsed before that assertion runs.
Keep transport and embedded encoding distinct in diagnostics. A request can arrive with an ordinary JSON HTTP body while one string inside that body uses Base64. The schema annotation does not request HTTP decompression or describe the entire response representation. If middleware also supports compressed request bodies, exercise that transport layer separately and verify that its decoded-body limit is applied before the JSON parser receives the request.
Do not turn a content label into an unbounded dispatch mechanism. A caller-controlled media type should not select an arbitrary plugin or launch a network fetch. Prefer a small supported set configured by the service. Unknown or mismatched encodings should produce a reviewed content-policy result rather than a fallback that tries several decoders until something appears to work.
3. Do decoded bytes become text and JSON through explicit parsing rules?
After decoding, test text decoding and embedded JSON parsing as separate boundaries from Base64 syntax. Valid encoded bytes can contain invalid UTF-8 or text that is not JSON. Select the intended character encoding, reject unsupported content deliberately, and keep parser extensions and resource limits visible rather than assuming a successful decoder produced a usable application object.
For an application/json payload in this example, decode bytes as UTF-8 using strict error handling, then parse the resulting text. Base64 for an HTML fragment can be perfectly valid Base64 while failing this JSON parser. Likewise, encoded bytes containing an invalid UTF-8 sequence should fail before the JSON parser receives a string.
The Python JSON documentation describes decoding behavior and implementation extensions. The focused helper below rejects non-JSON numeric constants such as NaN through parse_constant. That is a deliberate parser policy; do not assume every convenience decoder rejects those values in its default configuration.
import json
def reject_constant(_value):
raise ValueError("Non-JSON numeric constant")
def parse_embedded(raw):
text = raw.decode('utf-8', errors='strict')
return json.loads(text, parse_constant=reject_constant)
assert parse_embedded(b'{"name":"Ada"}') == {"name":"Ada"}
This helper is not a complete hostile-input parser. Decide how duplicate object names, deeply nested data, and numeric precision are handled by your application. A byte limit reduces the input size but does not prove every parser operation meets your execution budget. Keep those constraints in the endpoint’s broader input policy and test the settings actually used in deployment.
Do not recursively parse every string that happens to look like JSON. A name field containing braces can be ordinary user text. Processing should occur only at the explicitly selected document location and under the trusted contract. Include a valid inner object whose name contains JSON-looking text and assert that the name stays a string. This fixture catches generic helpers that keep decoding or parsing until they obtain a different shape.
Use the JSON Viewer to inspect a sanitized parsed inner document when debugging. It helps reveal the resulting structure, but does not run this decoding pipeline, enforce its limits, or validate the inner schema. Keep the original encoded fixture and expected processing stage in the automated test.
4. Does the inner validator evaluate the parsed document rather than the encoded string?
Apply the intended inner schema only after the embedded bytes have decoded and parsed successfully. Test a complete valid object, a missing required field, a wrong field type, and an unexpected property. Keep the inner outcome separate from outer validity, and confirm that the validator receives the parsed object rather than the Base64 text or raw decoded bytes.
from jsonschema import Draft202012Validator
inner_schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
"additionalProperties": False,
}
Draft202012Validator.check_schema(inner_schema)
inner_validator = Draft202012Validator(inner_schema)
assert inner_validator.is_valid({"name": "Ada"})
assert not inner_validator.is_valid({})
The validator interface provides schema checking and instance evaluation separately. Validate the inner schema during setup too. An invalid embedded contract is a service configuration defect, not evidence that a customer supplied the wrong data. The same distinction applies when contracts are generated from another source.
The example duplicates the inner definition visibly to make the stages easy to inspect. In a real implementation, keep one trusted definition and test that the schema’s contentSchema and the application’s selected inner contract stay aligned. If they drift, documentation can advertise one shape while the processor accepts another.
Maintain a clear location convention for inner failures. A missing name belongs to the parsed embedded object, not to a member directly inside the original outer document. An outer pointer to data can identify the container, with separate inner diagnostics identifying the decoded document location. Flattening them into a single path can make a client highlight a field that does not exist in the submitted JSON. Test those locations if your API exposes them.
ContentSchema without the corresponding contentMediaType should not be treated as an instruction to guess a parser. Choose an explicit supported policy instead. A schema associated with an embedded document also does not establish authenticity or permission to act on it. A structurally valid command still needs the application’s authorization and business checks before execution.
5. Does the application gate execution on the intended content result without changing outer semantics?
Test the full processing pipeline with stage-specific outcomes and ensure failed embedded content cannot reach business logic when the endpoint requires valid inner data. Preserve the original outer instance, and do not relabel its schema result after a later stage fails. The application’s acceptance decision can depend on several results while each diagnostic remains accurate about what was evaluated.
def read_embedded(document, outer_validator):
if not outer_validator.is_valid(document):
raise ValueError("outer_schema")
try:
raw = base64.b64decode(document['data'], validate=True)
except (binascii.Error, ValueError):
raise ValueError("encoding") from None
if len(raw) > 256:
raise ValueError("decoded_size")
try:
parsed = parse_embedded(raw)
except (UnicodeDecodeError, ValueError):
raise ValueError("content_syntax") from None
if not inner_validator.is_valid(parsed):
raise ValueError("content_schema")
return parsed
Construct outer_validator from the schema in check one. This application helper deliberately handles one fixed Base64-to-JSON policy; it is not a general interpreter for arbitrary content annotations. Save fixtures for every named failure stage and assert that the operation is not invoked for those failures. Add a valid control that reaches the handler once with the expected parsed object.
For diagnostics, avoid echoing the encoded input or decoded content into public errors. Encoding is not secrecy, and even a short payload can contain private information. The schema error-reporting tests explain how to separate internal details from a stable public response. Keep request identifiers and approved stage codes rather than relying on raw exception dumps.
Finally, test the real endpoint adapter, not only this helper. Confirm that decoding is explicitly enabled by application policy, that no unexpected parser is selected from untrusted metadata, and that the validated parsed object is the one passed downstream. A passing outer schema is useful evidence about the container; it becomes evidence about the embedded document only after the distinct processing checks have actually run.