Skip to main content

JSONvv

JSON value validator

Overview

This is a simple JSON schema validator library. It was created for Camoufox to validate passed user configurations. Because I found it useful for other projects, I decided to extract it into a separate library.

JSONvv's syntax parser is written in pure Python. It does not rely on any dependencies.

Example

Configuration Validator
config = {
    "username": "johndoe",
    "email": "johndoe@example.com",
    "age": 30,
    "chat": "Hello world!",
    "preferences": {
        "notifications": True,
        "theme": "dark"
    },
    "allowed_commands": [
        "/help", "/time", "/weather"
    ],
    "location": [40.7128, -74.0060],
    "hobbies": [
        {
            "name": "Traveling",
            "cities": ["Paris", "London"]
        },
        {
            "name": "reading",
            "hours": {
                "Sunday": 2,
                "Monday": 3,
            }
        }
    ]
}
validator = {
    "username": "str",  # Basic username
    "email": "str[/\S+@\S+\.\S+/]",  # Validate emails
    "age": "int[>=18]",  # Age must be 18 or older
    "chat": "str | nil",  # Optional chat message
    "preferences": {
        "notifications": "bool",
        "theme": "str[light, dark] | nil",  # Optional theme
    },
    # Commands must start with "/", but not contain "sudo"
    "allowed_commands": "array[str[/^//] - str[/sudo/]]",
    # Validate coordinate ranges
    "location": "tuple[double[-90 - 90], double[-180 - 180]]",
    # Handle an array of hobby types
    "hobbies": "array[@traveling | @other, >=1]",
    "@traveling": {
        # Require 1 or more cities/countries iff name is "Traveling"
        "*name,type": "str[Traveling]",
        "*cities,countries": "array[str[A-Za-z*], >=1]",
    },
    "@other": {
        "name,type": "str - str[Traveling]",  # Non-traveling types
        # If hour(s) is specified, require days have >0 hours
        "/hours?/": {
            "*/day$/": "int[>0]"
        }
    }
}

Then, validate the configuration like this:

from jsonvv import JsonValidator, JvvRuntimeException

val = JsonValidator(validator)
try:
   val.validate(config)
except JvvRuntimeException as exc:
   print("Failed:", exc)
else:
   print('Config is valid!')

Table of Contents


Keys Syntax

Dictionary keys can be specified in several possible ways:

  • "key": "type"
  • "key1,key2,key3": "type"
  • "/key\d+/": "type"
  • "*required_key": "type"

Regex patterns

To use regex in a key, wrap it in / ... /.

Syntax:

"/key\d+/": "type"

Lists of possible values

To specify a list of keys, use a comma-separated string.

Syntax:

"key1,key2,key3": "type"
"/k[ey]{2}1/,key2": "type"

To escape a comma, use !.

Required fields (*)

Fields marked with * are required. The validation will fail without them.

Syntax:

"*key1": "type"
"*/key\d+/": "type"

Grouping keys ($)

Fields that end with $group_name are grouped together. If one of the keys is set, all of the keys in the group must also be set as well.

Syntax:

"isEnabled$group1": "bool"
"value$group1": "int[>0]"

This will require both value is set if and only if isEnabled is set.

Multiple $ can be used to create more complex group dependencies.


Supported Types

String (str)

Represents a string value. Optionally, you can specify a regex pattern that the string must match.

Syntax:

  • Basic string: "str"
  • With regex pattern: "str[regex_pattern]"
  • The escape character for regex is \, and for commas is _.

Arguments:

  • regex_pattern: A regular expression that the string must match. If not specified, any string is accepted.

Examples:

  1. Basic string:

    "username": "str"
    

    Accepts any string value for the key username.

  2. String with regex pattern:

    "fullname": "str[/[A-Z][a-z]+ [A-Z][a-z]+/]"
    

    Accepts a string that matches the pattern of a first and last name starting with uppercase letters.

Integer (int)

Represents an integer value. You can specify conditions like exact values, ranges, and inequalities.

Syntax:

  • Basic integer: "int"
  • With conditions: "int[conditions]"

Arguments:

  • conditions: A comma-separated list of conditions.

Condition Operators:

  • ==: Equal to a specific value.
  • >=: Greater than or equal to a value.
  • <=: Less than or equal to a value.
  • >: Greater than a value.
  • <: Less than a value.
  • range: A range between two values (inclusive).

Examples:

  1. Basic integer:

    "age": "int"
    

    Accepts any integer value for the key age.

  2. Integer with conditions:

    "userage": "int[>=0, <=120]"
    

    Accepts integer values between 0 and 120 inclusive.

  3. Specific values and ranges

    "rating": "int[1-5]"
    "rating": "int[1,2,3,4-5]"
    

    Accepts integer values 1, 2, 3, 4, or 5.

  4. Ranges with negative numbers:

    "rating": "int[-100 - -90]"
    

    Accepts integer values from -100 to -90.

Double (double)

Represents a floating-point number. Supports the same conditions as integers.

Syntax:

  • Basic double: "double"
  • With conditions: "double[conditions]"

Arguments:

  • conditions: A comma-separated list of conditions.

Examples:

  1. Basic double:

    "price": "double"
    

    Accepts any floating-point number for the key price.

  2. Double with conditions:

    "percentage": "double[>=0.0,<=100.0]"
    

    Accepts double values between 0.0 and 100.0 inclusive.

Boolean (bool)

Represents a boolean value (True or False).

Syntax:

"isActive": "bool"

Accepts a boolean value for the key isActive.

Array (array)

Represents a list of elements of a specified type. You can specify conditions on the length of the array.

Syntax:

  • Basic array: "array[element_type]"
  • With length conditions: "array[element_type,length_conditions]"

Arguments:

  • element_type: The type of the elements in the array.
  • length_conditions: Conditions on the array length (same as integer conditions).

Examples:

  1. Basic array:

    "tags": "array[str]"
    

    Accepts a list of strings for the key tags.

  2. Array with length conditions:

    "scores": "array[int[>=0,<=100],>=1,<=5]"
    

    Accepts a list of 1 to 5 integers between 0 and 100 inclusive.

  3. Fixed-length array:

    "coordinates": "array[double, 2]"
    

    Accepts a list of exactly 2 double values.

  4. More complex restraints:

    "coordinates": "array[array[int[>0]] - tuple[1, 1]], 2]"
    

Tuple (tuple)

Represents a fixed-size sequence of elements of specified types.

Syntax:

"tuple[element_type1, element_type2]"

Arguments:

  • element_typeN: The type of the Nth element in the tuple.

Examples:

  1. Basic tuple:

    "point": "tuple[int, int]"
    

    Accepts a tuple or list of two integers.

  2. Tuple with mixed types:

    "userInfo": "tuple[str, int, bool]"
    

    Accepts a tuple of a string, an integer, and a boolean.

Nested Dictionaries

Represents a nested dictionary structure. Dictionaries are defined using Python's dictionary syntax {} in the type definitions.

Syntax:

"settings": {
    "volume": "int[>=0,<=100]",
    "brightness": "int[>=0,<=100]",
    "mode": "str"
}

Usage:

  • Define the expected keys and their types within the dictionary.
  • You can use all the supported types for the values.

Examples:

  1. Nested dictionary:

    "user": {
        "name": "str",
        "age": "int[>=0]",
        "preferences": {
            "theme": "str",
            "notifications": "bool"
        }
    }
    

    Defines a nested dictionary structure for the key user.

Nil (nil)

Represents a None value.

Syntax:

"optionalValue": "int | nil"

Usage:

  • Use nil to allow a value to be None.
  • Often used with union types to specify optional values.

Any (any)

Represents any value.

Syntax:

"metadata": "any"

Usage:

  • Use any when any value is acceptable.
  • Useful for keys where the value is not constrained.

Type References (@)

Allows you to define reusable types and reference them.

Syntax:

  • Define a named type:

    "@typeName": "type_definition"
    
  • Reference a named type:

    "key": "@typeName"
    

Examples:

  1. Defining and using a named type:

    "@positiveInt": "int[>0]"
    "userId": "@positiveInt"
    

    Defines a reusable type @positiveInt and uses it for the key userId.


Advanced Features

Subtracting Domains (-)

Allows you to specify that a value should not match a certain type or condition.

Syntax:

"typeA - typeB"

Usage:

  • The value must match typeA but not typeB.

Examples:

  1. Excluding certain strings:

    "message": "str - str[.*error.*]"
    

    Accepts any string that does not match the regex pattern .*error.*.

  2. Excluding a range of numbers:

    "score": "int[0-100] - int[>=90]"
    

    Accepts integers between 0 and 100, excluding values greater than or equal to 90.

  3. Excluding multiple types:

    "score": "int[>0,<100] - int[>90] - int[<10]"
    # Union, then subtraction:
    "score": "int[>0,<100] - int[>90] | int[<10]"
    "score": "int[>0,<100] - (int[>90] | int[<10])"  # same thing
    # Use parenthesis to run subtraction first
    "score": "int[>0,<50] | (int[<100] - int[<10])"
    "score": "(int[<100] - int[<10]) | int[>0,<50]"
    

    Note: Union is handled before subtraction.

  4. Allowing all but a specific value:

    "specialNumber": "any - int[0]"
    

Union Types (|)

Allows you to specify that a value can be one of multiple types.

Syntax:

"typeA | typeB | typeC"

Usage:

  • The value must match at least one of the specified types.

Examples:

  1. Multiple possible types:

    "data": "int | str | bool"
    

    Accepts an integer, string, or boolean value for the key data.

  2. Combining with arrays:

    "mixedList": "array[int | str]"
    

    Accepts a list of integers or strings.

Conditional Ranges and Values

Specifies conditions that values must satisfy, including ranges and specific values.

Syntax:

  • Greater than: ">value"
  • Less than: "<value"
  • Greater than or equal to: ">=value"
  • Less than or equal to: "<="value"
  • Range: "start-end"
  • Specific values: "value1,value2,value3"

Examples:

  1. Integer conditions:

    "level": "int[>=1,<=10]"
    

    Accepts integers from 1 to 10 inclusive.

  2. Double with range:

    "latitude": "double[-90.0 - 90.0]"
    

    Accepts doubles between -90.0 and 90.0 inclusive.

  3. Specific values:

    "status": "int[1,2,3]"
    

    Accepts integers that are either 1, 2, or 3.


Error Handling

graph TD
    Exception --> JvvException
    JvvException --> JvvRuntimeException
    JvvException --> JvvSyntaxError

    JvvRuntimeException --> UnknownProperty["UnknownProperty<br/><small>Raised when a key in config<br/>isn't defined in property types</small>"]
    JvvRuntimeException --> InvalidPropertyType["InvalidPropertyType<br/><small>Raised when a value doesn't<br/>match its type definition</small>"]
    InvalidPropertyType --> MissingRequiredKey["MissingRequiredKey<br/><small>Raised when a required key<br/>is missing from config</small>"]
    MissingRequiredKey --> MissingGroupKey["MissingGroupKey<br/><small>Raised when some keys in a<br/>property group are missing</small>"]

    JvvSyntaxError --> PropertySyntaxError["PropertySyntaxError<br/><small>Raised when property type<br/>definitions have syntax errors</small>"]

    classDef base fill:#eee,stroke:#333,stroke-width:2px;
    classDef jvv fill:#d4e6f1,stroke:#2874a6,stroke-width:2px;
    classDef runtime fill:#d5f5e3,stroke:#196f3d,stroke-width:2px;
    classDef syntax fill:#fdebd0,stroke:#b9770e,stroke-width:2px;
    classDef error fill:#fadbd8,stroke:#943126,stroke-width:2px;

    class Exception base;
    class JvvException jvv;
    class JvvRuntimeException,JvvSyntaxError runtime;
    class PropertySyntaxError syntax;
    class UnknownProperty,InvalidPropertyType,MissingRequiredKey,MissingGroupKey error;

Types

  • str: Basic string type.

    • Arguments:
      • regex_pattern (optional): A regex pattern the string must match.
    • Example: "str[^[A-Za-z]+$]"
  • int: Integer type with conditions.

    • Arguments:
      • conditions: Inequalities (>=, <=, >, <), specific values (value1,value2), ranges (start-end).
    • Example: "int[>=0,<=100]"
  • double: Double (floating-point) type with conditions.

    • Arguments:
      • Same as int.
    • Example: "double[>0.0]"
  • bool: Boolean type.

    • Arguments: None.
    • Example: "bool"
  • array: Array (list) of elements of a specified type.

    • Arguments:
      • element_type: Type of elements in the array.
      • length_conditions (optional): Conditions on the array length.
    • Example: "array[int[>=0],>=1,<=10]"
  • tuple: Fixed-size sequence of elements of specified types.

    • Arguments:
      • List of element types.
    • Example: "tuple[str, int, bool]"
  • nil: Represents a None value.

    • Arguments: None.
    • Example: "nil"
  • any: Accepts any value.

    • Arguments: None.
    • Example: "any"
  • Type References: Reusable type definitions.

    • Arguments:
      • @typeName: Reference to a named type.
    • Example:
      • Define: "@positiveInt": "int[>0]"
      • Use: "userId": "@positiveInt"

Type Combinations

  • Union Types (|): Value must match one of multiple types.

    • Syntax: "typeA | typeB"
    • Example: "str | int"
  • Subtracting Domains (-): Value must match typeA but not typeB.

    • Syntax: "typeA - typeB"
    • Example: "int - int[13]" (any integer except 13)

Escaping Characters

  • !: Escapes commas, slashes, and other jsonvv characters within strings.
  • \: Escapes within a regex pattern.

Metadata

Release files for jsonvv 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jsonvv 0.2.2
File Size Uploaded
jsonvv-0.2.2.tar.gz 17.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jsonvv 0.2.2
File Interpreter ABI Platform
jsonvv-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 32.5 kB

Release files / jsonvv-0.2.2.tar.gz

Download URL jsonvv-0.2.2.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0eec3a6515ce05387d73d412a7505318136267a511cbdd4dbeb63959a1a751c6
BLAKE2b-256 checksum
How to use checksums
0a01d7a719321baf7a93d444054840d9ca17c4bd5ccde58cb7af7b7e620762c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.1 CPython/3.12.4

Release files / jsonvv-0.2.2-py3-none-any.whl

Download URL jsonvv-0.2.2-py3-none-any.whl
Size 15.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bb574c7c3cea87106516e98f67dbcd7fde59a0e5d3ad0cef2ddb6cc3f9c9102f
BLAKE2b-256 checksum
How to use checksums
b314e69b23dc5e7b69aa5b2a4e0f5d60ca280865bebb9e7fd4db4974574e5589
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.1 CPython/3.12.4

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page