Skip to main content

JSON value validator

Project description

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.


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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jsonvv-0.2.0.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jsonvv-0.2.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file jsonvv-0.2.0.tar.gz.

File metadata

  • Download URL: jsonvv-0.2.0.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for jsonvv-0.2.0.tar.gz
Algorithm Hash digest
SHA256 110354ae5ee394e97a2f74c124a8668e699d37770d8dc8bbec927949faabefd4
MD5 6f0184f5f2267462f18b0121d2eaaf3b
BLAKE2b-256 dad4944b12246b06360ed3fc1fbd184fdcf76112d70f45dbfdc05a30f56f6ec9

See more details on using hashes here.

File details

Details for the file jsonvv-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: jsonvv-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for jsonvv-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f4b9458af3fbf3e673a3c85726c6cbaa186e4bbc11f1b1aec53903d9466d7a22
MD5 6ed863a50d98721de843cc504a808301
BLAKE2b-256 fa2ccc2a0558720a70856bb32e9a69f7234734a888b9f07772d36c85564a13ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page