Reason About Nested JSON Schema Validation
PinterestSoftware Engineer · OnsiteLast updated
Design and implement a recursive JSON schema validator that checks nested objects and arrays, enforces required properties, and returns precise failure paths for validation errors.
Problem Statement
Configuration services and event ingestion pipelines often validate incoming JSON payloads against a predefined schema before processing. Implement a lightweight schema validator that recursively checks a nested JSON-like structure (composed of Python dict, list, str, int, float, bool, and None) against a simplified JSON Schema definition.
Schema Specification
A schema is a dictionary that can contain the following keywords:
"type": A string specifying the expected type:"object","array","string","number"(accepts bothintandfloat, but notbool),"boolean", or"null"."properties": A dictionary mapping property names to sub-schemas (applicable whentypeis"object"). Note that undeclared properties in the payload are allowed."required": A list of string keys that must exist in an object (applicable whentypeis"object")."items": A sub-schema defining the expected schema for all elements in an array (applicable whentypeis"array").
Requirements
Write a function validate_schema(instance: Any, schema: dict) -> list[str] that returns a list of error strings. If the instance satisfies the schema, return an empty list [].
Each error message must follow the format "{path}: {message}", where:
{path}is the dot/bracket notation path from the root. Use"$"for the root object itself,"$.field"for object properties, and"$[index]"(e.g.,"$.users[0].name") for array elements.{message}must be:"expected , got "for type mismatches (actual types:"object","array","string","number","boolean","null")."missing required property ''"if a required property is absent.
Errors should be collected via depth-first search (DFS) traversal in a deterministic order (keys visited in schema definition order; array elements visited from index 0 upward).
Function Signature
def validate_schema(instance: Any, schema: dict) -> list[str]:
pass
Examples
Example 1
schema = {
"type": "object",
"required": ["id", "tags"],
"properties": {
"id": {"type": "number"},
"tags": {
"type": "array",
"items": {"type": "string"}
}
}
}
payload = {
"id": "101",
"tags": ["pinned", 42]
}
# Output:
# [
# "$.id: expected number, got string",
# "$.tags[1]: expected string, got number"
# ]
Example 2
schema = {
"type": "object",
"required": ["user"],
"properties": {
"user": {
"type": "object",
"required": ["email"]
}
}
}
payload = {"user": {}}
# Output:
# [
# "$.user: missing required property 'email'"
# ]
Constraints & Edge Cases
- In Python,
isinstance(True, int)isTrue. Ensure booleans are strictly identified as"boolean"and not"number". - If a parent object fails a type check, do not attempt to validate its
propertiesorrequiredfields. - An instance value of
Nonemaps to JSON type"null".
Hints
Hint 1
Write a helper function to resolve Python runtime values to your target JSON type names, taking care of Python's bool/int subclass relationship.
Hint 2
Perform type validation first; if the current node fails its type check, record the error and return early without traversing its children.
Hint 3
Construct the path string dynamically as you recurse: use '$' for root, '.key' for object fields, and '[i]' for array indices.
Follow-up questions
- How would you handle raw JSON strings with potential duplicate object keys (e.g., `{"a": 1, "a": 2}`), given Python's standard `json.loads` silently overwrites them?
- How would you extend this design to support schema composition keywords like `oneOf`, `anyOf`, and `$ref` recursive references without causing infinite loops?
- If validation is run on high-throughput event streams, how can you optimize this validator to fail-fast or pre-compile schemas into bytecode/closures?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.