mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-11 03:12:05 -08:00
728 lines
15 KiB
Markdown
728 lines
15 KiB
Markdown
# 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
|
|
|
|
<table>
|
|
<tr>
|
|
<th width="50%">Configuration</th>
|
|
<th width="50%">Validator</th>
|
|
</tr>
|
|
<tr>
|
|
<td width="50%">
|
|
|
|
```python
|
|
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,
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
</td>
|
|
<td width="50%">
|
|
|
|
```python
|
|
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]"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr width=50>
|
|
|
|
Then, validate the configuration like this:
|
|
|
|
```python
|
|
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
|
|
|
|
- [Key Syntax](#key-syntax)
|
|
- [Regex patterns](#regex-patterns)
|
|
- [Lists of possible values](#lists-of-possible-values)
|
|
- [Required fields (`*`)](#required-fields-)
|
|
- [Grouping keys (`$`)](#grouping-keys-)
|
|
- [Supported Types](#supported-types)
|
|
- [String (`str`)](#string-str)
|
|
- [Integer (`int`)](#integer-int)
|
|
- [Double (`double`)](#double-double)
|
|
- [Boolean (`bool`)](#boolean-bool)
|
|
- [Array (`array`)](#array-array)
|
|
- [Tuple (`tuple`)](#tuple-tuple)
|
|
- [Nested Dictionaries](#nested-dictionaries)
|
|
- [Nil (`nil`)](#nil-nil)
|
|
- [Any (`any`)](#any-any)
|
|
- [Required fields (`*`)](#required-fields-)
|
|
- [Type References (`@`)](#type-references-)
|
|
- [Advanced Features](#advanced-features)
|
|
- [Subtracting Domains (`-`)](#subtracting-domains--)
|
|
- [Union Types (`|`)](#union-types-)
|
|
- [Conditional Ranges and Values](#conditional-ranges-and-values)
|
|
- [Error Handling](#error-handling)
|
|
|
|
---
|
|
|
|
## 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:**
|
|
|
|
```python
|
|
"/key\d+/": "type"
|
|
```
|
|
|
|
### Lists of possible values
|
|
|
|
To specify a list of keys, use a comma-separated string.
|
|
|
|
**Syntax:**
|
|
|
|
```python
|
|
"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:**
|
|
|
|
```python
|
|
"*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:**
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"username": "str"
|
|
```
|
|
|
|
Accepts any string value for the key `username`.
|
|
|
|
2. String with regex pattern:
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"age": "int"
|
|
```
|
|
|
|
Accepts any integer value for the key `age`.
|
|
|
|
2. Integer with conditions:
|
|
|
|
```python
|
|
"userage": "int[>=0, <=120]"
|
|
```
|
|
|
|
Accepts integer values between 0 and 120 inclusive.
|
|
|
|
3. Specific values and ranges
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"price": "double"
|
|
```
|
|
|
|
Accepts any floating-point number for the key `price`.
|
|
|
|
2. Double with conditions:
|
|
|
|
```python
|
|
"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:**
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"tags": "array[str]"
|
|
```
|
|
|
|
Accepts a list of strings for the key `tags`.
|
|
|
|
2. Array with length conditions:
|
|
|
|
```python
|
|
"scores": "array[int[>=0,<=100],>=1,<=5]"
|
|
```
|
|
|
|
Accepts a list of 1 to 5 integers between 0 and 100 inclusive.
|
|
|
|
3. Fixed-length array:
|
|
|
|
```python
|
|
"coordinates": "array[double, 2]"
|
|
```
|
|
|
|
Accepts a list of exactly 2 double values.
|
|
|
|
4. More complex restraints:
|
|
```python
|
|
"coordinates": "array[array[int[>0]] - tuple[1, 1]], 2]"
|
|
```
|
|
|
|
### Tuple (`tuple`)
|
|
|
|
Represents a fixed-size sequence of elements of specified types.
|
|
|
|
**Syntax:**
|
|
|
|
```python
|
|
"tuple[element_type1, element_type2]"
|
|
```
|
|
|
|
**Arguments:**
|
|
|
|
- `element_typeN`: The type of the Nth element in the tuple.
|
|
|
|
**Examples:**
|
|
|
|
1. Basic tuple:
|
|
|
|
```python
|
|
"point": "tuple[int, int]"
|
|
```
|
|
|
|
Accepts a tuple or list of two integers.
|
|
|
|
2. Tuple with mixed types:
|
|
|
|
```python
|
|
"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:**
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"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:**
|
|
|
|
```python
|
|
"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:**
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"@typeName": "type_definition"
|
|
```
|
|
|
|
- Reference a named type:
|
|
|
|
```python
|
|
"key": "@typeName"
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
1. Defining and using a named type:
|
|
|
|
```python
|
|
"@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:**
|
|
|
|
```python
|
|
"typeA - typeB"
|
|
```
|
|
|
|
**Usage:**
|
|
|
|
- The value must match `typeA` but not `typeB`.
|
|
|
|
**Examples:**
|
|
|
|
1. Excluding certain strings:
|
|
|
|
```python
|
|
"message": "str - str[.*error.*]"
|
|
```
|
|
|
|
Accepts any string that does not match the regex pattern `.*error.*`.
|
|
|
|
2. Excluding a range of numbers:
|
|
|
|
```python
|
|
"score": "int[0-100] - int[>=90]"
|
|
```
|
|
|
|
Accepts integers between 0 and 100, excluding values greater than or equal to 90.
|
|
|
|
3. Excluding multiple types:
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"specialNumber": "any - int[0]"
|
|
```
|
|
|
|
### Union Types (`|`)
|
|
|
|
Allows you to specify that a value can be one of multiple types.
|
|
|
|
**Syntax:**
|
|
|
|
```python
|
|
"typeA | typeB | typeC"
|
|
```
|
|
|
|
**Usage:**
|
|
|
|
- The value must match at least one of the specified types.
|
|
|
|
**Examples:**
|
|
|
|
1. Multiple possible types:
|
|
|
|
```python
|
|
"data": "int | str | bool"
|
|
```
|
|
|
|
Accepts an integer, string, or boolean value for the key `data`.
|
|
|
|
2. Combining with arrays:
|
|
|
|
```python
|
|
"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:
|
|
|
|
```python
|
|
"level": "int[>=1,<=10]"
|
|
```
|
|
|
|
Accepts integers from 1 to 10 inclusive.
|
|
|
|
2. Double with range:
|
|
|
|
```python
|
|
"latitude": "double[-90.0 - 90.0]"
|
|
```
|
|
|
|
Accepts doubles between -90.0 and 90.0 inclusive.
|
|
|
|
3. Specific values:
|
|
|
|
```python
|
|
"status": "int[1,2,3]"
|
|
```
|
|
|
|
Accepts integers that are either 1, 2, or 3.
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
```mermaid
|
|
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.
|