Katkat

Design a JSON Schema Validator

DocuSignSoftware Engineer · Onsite

Last updated

Design and implement a modular JSON schema validator that verifies data against structural schemas, supporting primitive types, nested objects, arrays, regex patterns, numeric ranges, and strict field controls.

Problem Statement

In document processing and API integration platforms like DocuSign, incoming payloads must be strictly validated against predefined contracts before execution.

Implement a JSONSchemaValidator class that validates arbitrary JSON-like data against a simplified JSON schema specification. The validator must return whether the data is valid and a list of human-readable error descriptions if it is not.

Supported Schema Keywords

  1. type: Matches target data type: "string", "number" (accepts float or int), "boolean", "object", or "array".
  2. Numeric constraints (for type: "number"):
    • minimum: value must be >= minimum.
    • maximum: value must be <= maximum.
  3. String constraints (for type: "string"):
    • pattern: a regular expression string that the value must match (via full or partial match per regex definition).
  4. Object constraints (for type: "object"):
    • properties: a dictionary mapping property names to sub-schemas.
    • required: a list of property names that must be present.
    • additionalProperties: boolean. If False, no fields outside of properties are allowed (defaults to True).
  5. Array constraints (for type: "array"):
    • items: a sub-schema that every element in the array must satisfy.

Class Interface

class JSONSchemaValidator:
    def validate(self, schema: dict, data: any) -> tuple[bool, list[str]]:
        """
        Validates `data` against `schema`.
        Returns (True, []) if valid, or (False, [error_messages]) if invalid.
        """
        pass

Example 1

schema = {
    "type": "object",
    "required": ["name", "age"],
    "additionalProperties": False,
    "properties": {
        "name": {"type": "string", "pattern": r"^[A-Za-z ]+$"},
        "age": {"type": "number", "minimum": 18, "maximum": 120}
    }
}

data = {"name": "Alice", "age": 25}
validator = JSONSchemaValidator()
is_valid, errors = validator.validate(schema, data)
# is_valid: True, errors: []

Example 2

data_invalid = {"name": "Alice", "age": 15, "extra": True}
is_valid, errors = validator.validate(schema, data_invalid)
# is_valid: False
# errors: ["age must be >= 18", "unexpected property: extra"]

Constraints

  • The provided schema is guaranteed to be syntactically valid.
  • Nesting depth of objects/arrays will not exceed 20 levels.
  • data may contain None, bool, int, float, str, list, or dict.
  • Note: in Python, isinstance(True, int) evaluates to True. Make sure boolean values are not mistakenly identified as numbers.

Hints

Hint 1

Beware of Python's type hierarchy where `bool` is a subclass of `int`; check for `bool` explicitly before evaluating numeric types.

Hint 2

Use recursion for nested objects and arrays, passing down a field path (e.g., `user.addresses[0]`) to build informative error messages.

Hint 3

Structure your validation rules using the Strategy or Dispatch pattern (e.g., helper methods per keyword or type) so adding new validation keywords requires minimal code changes.

Follow-up questions

  • How would you extend this validator to support schema references (`$ref`) and circular definitions?
  • If regex patterns come from untrusted user schemas, how would you protect the validator against Regular Expression Denial of Service (ReDoS)?
  • How would you optimize validation if the same schema is executed across millions of requests per minute?