JsonSchema Compact Notation
===========================
[JSON Schema](https://json-schema.org/) is very useful to document and
validate inputs and outputs of JSON-based REST APIs. If you check
everything that goes into your program (requests, configuration
files…) and everything that goes out of it (responses), you'll catch a
lot of errors and regressions early. Moreover, the schema also acts
has a documentation for develpers and users alike, and a documentation
that _has_ to stay up-to-date: otherwise you get exceptions thrown
around!
Unfortunately, IMO JSON Schema is neither as concise nor as
human-readable as it should. Not as bad as XML schemas, but not great
either. As a demonstration, here's a schema for a tiny subset of
[GeoJSON](https://tools.ietf.org/html/rfc7946):
{ "type": "object",
"required": ["type", "geometry"],
"properties": {
"type": {"const": "Feature"},
"geometry": {
"anyOf": [
{"$ref": "#/definitions/point"},
{"$ref": "#/definitions/lineString"}]}},
"definitions": {
"coord": {
"type": "array",
"items": {"type": "number"},
"minItems": 2, "maxItems": 2},
"point": {
"type": "object",
"required": ["type", "coordinates"],
"properties": {
"type": {"const": "Point"},
"coordinates": {"$ref": "#/definitions/coord"}}},
"lineString": {
"type": "object",
"required": ["type", "coordinates"],
"properties": {
"type": {"const": "LineString"},
"coordinates": {
"type": "array",
"items": {"$ref": "#/definitions/coord"}}}}}}
Enter JSCN, a.k.a. "JSON Schema Compact Notation", which expresses the
exact same schema as follows:
{
type: "Feature",
geometry: <point> | <lineString>
}
where coord = [number*]{2}
and point = {type: "Point", coordinates: <coord>}
and lineString = {type: "LineString", coordinates: [<coord>*]}
Not only is it much shorter; hopefully it reads as well as an
informal documentation written for fellow developers.
JSCN allows to express most of JSON Schema, in a language which isn't
a subset of JSON, but is much more compact and human-readable. This
Python library implements a parser which translates JSCN into
JSON Schema, and encapsulates the result in a Python object allowing
actual validation through [the JSON Schema
library](https://python-jsonschema.readthedocs.io/).
Below is an informal description of the grammar. Fluency with JSON is
expected; familiarity with JSON Schema is probably not mandatory, but
won't hurt.
Informal grammar
----------------
### simple types
Litteral JSON types are accessible as keywords: `boolean`, `string`,
`integer`, `number`, `null`.
Regular expression strings are represented by `r`-prefixed litteral
strings, similar to Python's litterals: `r"^[0-9]+$"` converts into
`{"type": "string", "pattern": "^[0-9]+$"}` and matches strings which
conform to the regular expression. Beware that for JSON Schema, partial
matches are OK, for instance `r"[0-9]+"` does match `"foo123bar"`. Add
`"^…$"` markers to match the whole string.
Predefined string formats are represented by `f`-prefixed litteral
strings: `f"uri"` converts into `{"type": "string", "format":
"uri"}`. The list of currently predefined formats can be found for
instance at
[https://json-schema.org/understanding-json-schema/reference/string.html#format].
JSON constants are introduced between back-quotes: `` `123` ``
converts to `{"const": 123}` and will only match the number 123. If
several constants are joined with an `|` operator, they are translated
into a JSON Schema enum: `` `1`|`2` `` converts to `{"enum": [1,
2]}`. For litteral constants (strings, numbers, booleans, null),
backquotes aren't mandatory, i.e. `` `"foo"` `` and `"foo"` are
equivalent.
### Arrays
Arrays are described between square brackets:
* `[]` matches every possible array, and can also be written `array`.
* An homogeneous, non-empty array of integers is denoted `[integer+]`
* An homogeneous, possibly empty array of integers is denoted `[integer*]`
* An array starting with two booleans is denoted `[boolean, boolean]`.
It can also contain additional items after those two booleans, and those
items don't have to be booleans.
* In order to forbid additional items, add an `only` keyword at the beginning
of the array: `[only boolean, boolean]` will reject `[true, false, 1]`,
whereas `[boolean, boolean]` would have accepted it.
* Arrays support cardinal suffix between braces: `[]{7}` is an array
with exactly 7 elements, `[integer*]{3,8}` is an array with between 3
and 8 integers (inclusive), `[]{_, 9}` an array with at most 9
elements, `[string*]{4, _}` an array with at least 4 strings.
* A uniqueness constraint can be added with the `unique` prefix, as in
`[unique integer+]`, which accepts `[1, 2, 3]` but not `[1, 2, 1]`
since `1` occurs more than once.
Strings and integers also support cardinal suffixes, e.g. `string{16}`
(a string of 16 characters), `integer{_, 0xFFFF}` (an integer between
0 and 65533). Ranges and sizes are inclusive.
### Objects
Objects are described between curly braces:
* `{ }` matches every possible object, and can also be written
`object`.
* `{"bar": integer}` is an object with one field `"bar"` of type
integer, and possibly other fields.
* Quotes are optional around property names, if they are are valid
JavaScript identifiers other than `"_"` or `"only"`: it's legal to
write `{bar: integer}`.
* To prevent non-listed property names from being accepted, use a
prefix `only` just after the opening brace, as in `{only "bar":
integer}`.
* Property names can be forced to comply with a regex, by an `only
r"regex"` prefix, which can also be a reference to a definition:
`{only r"^[a-z]+$"}`, or the equivalent `{only <word>} where
word=r"^[a-z]$"+`. Beware that according to JSON Schema, even
explicitly listed property names must comply with the regex. For
instance, nothing can satisfy the schema `{only r"^[0-9]+$",
"except_this": _}`, because `r"^[0-9]+$"` doesn't match
`"except_this"`. To circumvent this limitation, you need to widen
the regex with an `"|"`, e.g. `{only r"^([0-9]+|except_this)$"}`,
or ``{only <key>} where key = `"except_this"` | r"^[0-9]+$"``.
* In addition to enforcing a regex on property names, one can also
enforce a type constraint on the associated values: `{only <word>:
integer}`. If you want a constraint on the type but not on the name,
the name can be replaced by an underscore wildcard: `{only _:
integer}`.
* A special type `forbidden`, equivalent to JSON Schema's `false`, can
be used to specifically forbid a property name: `{reserved_name?:
forbidden}`. Notice that the question mark is mandatory: otherwise,
it would both expect the property to exist, and accept no value in it.
### Definitions
Definitions can be used in the schema, and given with a suffix `where
name0 = def0 and ... and nameX=defX`. References to definitions are
put between angles, for instance `{author: <user_name>} where
user_name = r"^\w+$"`. When dumping the schema into actual JSON Schema,
unused definitions are pruned, and missing definitions cause an error.
Definitions can only occur at top-level, i.e.
`{foo: <bar>} where bar=number` is legal, but
`{foo: (<bar> where bar=number)}` is not.
### Operations between types
Types can be combined:
* With infix operator `&`: `A & B` is the type of objects which
respect both schemas `A` and `B`.
* With infix operator `|`: `A | B` is the type of objects which
respect at least one of the schemas `A` or `B`. `&` takes precedence
over `|`, i.e. `A & B | C & D` is to be read as `(A&B) | (C&D)`.
* With conditional expressions: `if A then B elif C then D else E`
will enforce constraint `B` if constraint `A` is met, enforce `D` if
`C` is met, or enforce `E` if neither `A` nor `C` are met. `elif`
and `else` parts are optional. For instance, `if {country: "USA"}
then {postcode: r"\d{5}(-\d{4})?"} else {postcode: string}` will
only check the postcode with the regex if the country is `"USA"`.
* Parentheses can be added to enforce precedences , e.g. `A & (B|C) & D`
* There is also an `not` operator: `{foo: not boolean}`.
### From Python
Combinations can also be performed on Python objects, e.g. the
following Python expression is OK: `Schema("{foo: number}") |
Schema("{bar: number}")`, and produces a schema equivalent to
`Schema("{foo: number}|{bar: number}")`. When definitions are merged
in Python with `|` or `&`, their definitions are merged as needed. If
a definition appears on both sides, it must be equal, i.e. one can
merge `{foo: <n>} where n=number` with `{bar: <n>} where n=number` but
not with `{foo: <n>} where n=integer`.
More formally
-------------
schema ::= type («where» definitions)?
definitions ::= identifier «=» type («and» identifier «=» type)*
type ::= type «&» type # allOf those types; takes precedence over «|».
| type «|» type # anyOf those types.
| «(» type «)» # parentheses to enforce precedence.
| «not» type # anything but this type.
| «`»json_litteral«`» # just this JSON constant value.
| «<»identifier«>» # identifier refering to the matching top-level definition.
| r"regular_expression" # String matched by this regex.
| f"format" # JSON Schema draft7 string format.
| «string» cardinal? # a string, with this cardinal constraint on number of chars.
| «integer» cardinal? # an integer within the range described by cardinal.
| «integer» «/» int # an integer which must be multiple of that int.
| «object» # any object.
| «array» # any array.
| «boolean» # any boolean.
| «null» # the null value.
| «number» # any number.
| «forbidden» # empty type (used mostly to disallow a property name).
| object # structurally described object.
| array # structurally described array.
| conditional # conditional if/then/else rule
cardinal ::= «{» int «}» # Exactly that number of chars / items / properties.
| «{» «_», int «}» # At most that number of chars / items / properties.
| «{» int, «_» «}» # At least that number of chars / items / properties.
| «{» int, int «}» # A number of chars / items / properties within this range.
object ::= «{» object_restriction? (object_key «?»? «:» type «,»...)* «}» cardinal?
# if «only» occurs without a regex, no extra property is allowed.
# if «only» occurs with a regex, all extra property names must match that regex.
# if «?» occurs, the preceding property is optional, otherwise it's required.
object_restriction ::= ø
# Only explicitly listed property names are accepted:
| «only»
# every property name must conform to regex/reference:
| «only» (r"regex" | «<»identifier«>»)
# non-listed property names must conform to regex, values to type:
| «only» (r"regex" | «<»identifier«>» | «_»)«:» type
object_key ::= identifier # Litteral property name.
| «"»string«"» # Properties which aren't identifiers must be quoted.
array ::= «[» «only»? «unique»? (type «,»)* («*»|«+»|ø) «]» cardinal?
# if «only» occurs, no extra item is allowed.
# if «unique» occurs, each array item must be different from every other.
# if «*» occurs, the last type can be repeated from 0 to any times.
# Every extra item must be of that type.
# if «+» occurs, the last type can be repeated from 1 to any times.
# Every extra item must be of that type.
conditional ::= «if» type «then» type («elif» type «then» type)* («else» type)?
int ::= /0x[0-9a-fA-F]+/ | /[0-9]+/
identifier ::= /[A-Za-z_][A-Za-z_0-9]*/
TODO
----
Some things that may be added in future versions:
* on numbers:
* ranges over floats (reusing cardinal grammar with float
boundaries)
* modulus constraints on floats `number/0.25`.
* exclusive ranges in addition to inclusive ones. May use returned
braces, e.g. `integer{0,0x100{` as an equivalent for
`integer{0,0xFF}`?
* ranges alone are treated as integer ranges, i.e. `{1, 5}` is a
shortcut for `integer{1, 5}`? Not sure whether it enhances
readability, and there would be a need for float support in
ranges then.
* combine string constraints: regex, format, cardinals... This can
already be achieved with operator `&`.
* try to embedded `#`-comments as `"$comment"`
* Implementation:
* bubble up `?` markers in grammar to the top level.
* Syntax sugar:
* optional marker: `foobar?` is equivalent to `foobar|null`. Not
sure whether it's worth it, the difference between a missing
field and a field holding `null` is most commonly not significant.
* check that references as `propertyNames` indeed point at string
types.
* make keyword case-insensitive?
* treat `{foo: forbidden}` as `{foo?: forbidden}` as it's the only
thing that would make sense?
* better error messages, on incorrect grammars, and on non-validating
JSON data.
* reciprocal feature: try and translate a JSON Schema into a shorter
and more readable JSCN source.
Usage
-----
### From command line
$ echo -n '[integer*]' | jscn -
{ "type": "array",
"items": {"type": "integer"},
"$schema": "http://json-schema.org/draft-07/schema#"
}
$ jscn --help
usage: jscn [-h] [-o OUTPUT] [-v] [--version] [filename]
Convert from a compact DSL into full JSON Schema.
positional arguments:
filename Input file; use '-' to read from stdin.
optional arguments:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
Output file; defaults to stdout
-v, --verbose Verbose output
--version Display version and exit
### From Python API
Python's `jsonschema_cn` package exports two main constructors:
* `Schema()`, which compiles a source string into a schema object;
* `Definitions()`, which compiles a source string (a sequence of
definitions separated by keyword `and`, as in rule `definitions` of
the formal grammar.
Schema objects have a `jsonschema` property, which contains the Python
dict of the corresponding JSON Schema.
Schemas can be combined with Python operators `&` (`"allOf"`) and `|`
(`"anyOf"`). When they have definitions, those definition sets are
merged, and definition names must not overlap.
Schemas can also be combined with definitions through `|`, and
definitions can be combined together also with `|`.
>>> from jsonschema import Schema, Definitions
>>> defs = Definitions("""
>>> id = r"[a-z]+" and
>>> byte = integer{0,0xff}
>>> """)
>>> s = Schema("{only <id>: <byte>}") | defs
>>> s.jsonschema
ValueError: Missing definition for byte
>>> s = s | defs
>>> s.jsonschema
{"$schema": "http://json-schema.org/draft-07/schema#"
"type": "object",
"propertyNames": {"$ref": "#/definitions/id"},
"additionalProperties": {"$ref": "#/definitions/byte"},
"definitions": {
"id": {"type": "string", "pattern": "[a-z]+"},
"byte": {"type": "integer", "minimum": 0, "maximum": 255}
}
}
>>> Schema("[integer, boolean+]{4}").jsonschema
{ "$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"minItems": 4, "maxItems": 4,
"items": [{"type": "integer"}],
"additionalItems": {"type": "boolean"},
}
See also
--------
If you spend a lot of time dealing with complex JSON data structures,
you might also want to try [jsview](https://github.com/fab13n/jsview),
a smarter JSON formatter, which tries to effectively use both your
screen's width and height, by only inserting q carriage returns when
it makes sense:
$ cat >schema.cn <<EOF
{ only codes: [<byte>+], id: r"[a-z]+", issued: f"date"}
where byte = integer{0, 0xFF}
EOF
$ jscn schema.cn
{"type": "object", "required": ["codes", "id", "issued"], "properties": {
"codes": {"type": "array", "items": [{"$ref": "#/definitions/byte"}], "ad
ditionalItems": {"$ref": "#/definitions/byte"}}, "id": {"type": "string",
"pattern": "[a-z]+"}, "issued": {"type": "string", "format": "date"}}, "a
dditionalProperties": false, "definitions": {"byte": {"type": "integer",
"minimum": 0, "maximum": 255}}, "$schema": "http://json-schema.org/draft-
07/schema#"}
$ cat schema.cn | jscn - | jsview -
{
"type": "object",
"required": ["codes", "id", "issued"],
"properties": {
"codes": {
"type": "array",
"items": [{"$ref": "#/definitions/byte"}],
"additionalItems": {"$ref": "#/definitions/byte"}
},
"id": {"type": "string", "pattern": "[a-z]+"},
"issued": {"type": "string", "format": "date"}
},
"additionalProperties": false,
"definitions": {"byte": {"type": "integer", "minimum": 0, "maximum": 255}},
"$schema": "http://json-schema.org/draft-07/schema#"
}
Raw data
{
"_id": null,
"home_page": "http://github.com/fab13n/jsonschema_cn",
"name": "jsonschema-cn",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "DSL JSON schema jsonschema",
"author": "Fabien Fleutot",
"author_email": "fleutot@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/fe/c4/5096b92da3b5fc541901845876c2b589b84dc94e5f25443aacae1e2c8858/jsonschema_cn-0.28.tar.gz",
"platform": null,
"description": "JsonSchema Compact Notation\n===========================\n\n[JSON Schema](https://json-schema.org/) is very useful to document and\nvalidate inputs and outputs of JSON-based REST APIs. If you check\neverything that goes into your program (requests, configuration\nfiles\u2026) and everything that goes out of it (responses), you'll catch a\nlot of errors and regressions early. Moreover, the schema also acts\nhas a documentation for develpers and users alike, and a documentation\nthat _has_ to stay up-to-date: otherwise you get exceptions thrown\naround!\n\nUnfortunately, IMO JSON Schema is neither as concise nor as\nhuman-readable as it should. Not as bad as XML schemas, but not great\neither. As a demonstration, here's a schema for a tiny subset of\n[GeoJSON](https://tools.ietf.org/html/rfc7946):\n\n\n\t{ \"type\": \"object\",\n \"required\": [\"type\", \"geometry\"],\n \"properties\": {\n \"type\": {\"const\": \"Feature\"},\n \"geometry\": {\n \"anyOf\": [\n {\"$ref\": \"#/definitions/point\"},\n {\"$ref\": \"#/definitions/lineString\"}]}},\n \"definitions\": {\n \"coord\": {\n \"type\": \"array\",\n \"items\": {\"type\": \"number\"},\n \"minItems\": 2, \"maxItems\": 2},\n \"point\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"coordinates\"],\n \"properties\": {\n \"type\": {\"const\": \"Point\"},\n \"coordinates\": {\"$ref\": \"#/definitions/coord\"}}},\n \"lineString\": {\n \"type\": \"object\",\n \"required\": [\"type\", \"coordinates\"],\n \"properties\": {\n \"type\": {\"const\": \"LineString\"},\n \"coordinates\": {\n \"type\": \"array\",\n \"items\": {\"$ref\": \"#/definitions/coord\"}}}}}}\n\nEnter JSCN, a.k.a. \"JSON Schema Compact Notation\", which expresses the\nexact same schema as follows:\n\n { \n type: \"Feature\", \n geometry: <point> | <lineString>\n }\n where coord = [number*]{2}\n and point = {type: \"Point\", coordinates: <coord>}\n and lineString = {type: \"LineString\", coordinates: [<coord>*]}\n\nNot only is it much shorter; hopefully it reads as well as an\ninformal documentation written for fellow developers.\n\nJSCN allows to express most of JSON Schema, in a language which isn't\na subset of JSON, but is much more compact and human-readable. This\nPython library implements a parser which translates JSCN into\nJSON Schema, and encapsulates the result in a Python object allowing\nactual validation through [the JSON Schema\nlibrary](https://python-jsonschema.readthedocs.io/).\n\nBelow is an informal description of the grammar. Fluency with JSON is\nexpected; familiarity with JSON Schema is probably not mandatory, but\nwon't hurt.\n\nInformal grammar\n----------------\n\n### simple types\n\nLitteral JSON types are accessible as keywords: `boolean`, `string`,\n`integer`, `number`, `null`.\n\nRegular expression strings are represented by `r`-prefixed litteral\nstrings, similar to Python's litterals: `r\"^[0-9]+$\"` converts into\n`{\"type\": \"string\", \"pattern\": \"^[0-9]+$\"}` and matches strings which\nconform to the regular expression. Beware that for JSON Schema, partial\nmatches are OK, for instance `r\"[0-9]+\"` does match `\"foo123bar\"`. Add\n`\"^\u2026$\"` markers to match the whole string.\n\n\nPredefined string formats are represented by `f`-prefixed litteral\nstrings: `f\"uri\"` converts into `{\"type\": \"string\", \"format\":\n\"uri\"}`. The list of currently predefined formats can be found for\ninstance at\n[https://json-schema.org/understanding-json-schema/reference/string.html#format].\n\nJSON constants are introduced between back-quotes: `` `123` ``\nconverts to `{\"const\": 123}` and will only match the number 123. If\nseveral constants are joined with an `|` operator, they are translated\ninto a JSON Schema enum: `` `1`|`2` `` converts to `{\"enum\": [1,\n2]}`. For litteral constants (strings, numbers, booleans, null),\nbackquotes aren't mandatory, i.e. `` `\"foo\"` `` and `\"foo\"` are\nequivalent.\n\n### Arrays\n\nArrays are described between square brackets:\n\n* `[]` matches every possible array, and can also be written `array`.\n* An homogeneous, non-empty array of integers is denoted `[integer+]`\n* An homogeneous, possibly empty array of integers is denoted `[integer*]`\n* An array starting with two booleans is denoted `[boolean, boolean]`.\n It can also contain additional items after those two booleans, and those\n items don't have to be booleans.\n* In order to forbid additional items, add an `only` keyword at the beginning\n of the array: `[only boolean, boolean]` will reject `[true, false, 1]`,\n whereas `[boolean, boolean]` would have accepted it.\n* Arrays support cardinal suffix between braces: `[]{7}` is an array\n with exactly 7 elements, `[integer*]{3,8}` is an array with between 3\n and 8 integers (inclusive), `[]{_, 9}` an array with at most 9\n elements, `[string*]{4, _}` an array with at least 4 strings.\n* A uniqueness constraint can be added with the `unique` prefix, as in\n `[unique integer+]`, which accepts `[1, 2, 3]` but not `[1, 2, 1]`\n since `1` occurs more than once.\n\nStrings and integers also support cardinal suffixes, e.g. `string{16}`\n(a string of 16 characters), `integer{_, 0xFFFF}` (an integer between\n0 and 65533). Ranges and sizes are inclusive.\n\n### Objects\n\nObjects are described between curly braces:\n\n* `{ }` matches every possible object, and can also be written\n `object`.\n* `{\"bar\": integer}` is an object with one field `\"bar\"` of type\n integer, and possibly other fields.\n* Quotes are optional around property names, if they are are valid\n JavaScript identifiers other than `\"_\"` or `\"only\"`: it's legal to\n write `{bar: integer}`.\n* To prevent non-listed property names from being accepted, use a\n prefix `only` just after the opening brace, as in `{only \"bar\":\n integer}`.\n* Property names can be forced to comply with a regex, by an `only\n r\"regex\"` prefix, which can also be a reference to a definition:\n `{only r\"^[a-z]+$\"}`, or the equivalent `{only <word>} where\n word=r\"^[a-z]$\"+`. Beware that according to JSON Schema, even\n explicitly listed property names must comply with the regex. For\n instance, nothing can satisfy the schema `{only r\"^[0-9]+$\",\n \"except_this\": _}`, because `r\"^[0-9]+$\"` doesn't match\n `\"except_this\"`. To circumvent this limitation, you need to widen\n the regex with an `\"|\"`, e.g. `{only r\"^([0-9]+|except_this)$\"}`,\n or ``{only <key>} where key = `\"except_this\"` | r\"^[0-9]+$\"``.\n* In addition to enforcing a regex on property names, one can also\n enforce a type constraint on the associated values: `{only <word>:\n integer}`. If you want a constraint on the type but not on the name,\n the name can be replaced by an underscore wildcard: `{only _:\n integer}`.\n* A special type `forbidden`, equivalent to JSON Schema's `false`, can\n be used to specifically forbid a property name: `{reserved_name?:\n forbidden}`. Notice that the question mark is mandatory: otherwise,\n it would both expect the property to exist, and accept no value in it.\n\n### Definitions\n\nDefinitions can be used in the schema, and given with a suffix `where \nname0 = def0 and ... and nameX=defX`. References to definitions are\nput between angles, for instance `{author: <user_name>} where \nuser_name = r\"^\\w+$\"`. When dumping the schema into actual JSON Schema,\nunused definitions are pruned, and missing definitions cause an error.\nDefinitions can only occur at top-level, i.e. \n`{foo: <bar>} where bar=number` is legal, but\n`{foo: (<bar> where bar=number)}` is not.\n\n\n### Operations between types\n\nTypes can be combined:\n\n* With infix operator `&`: `A & B` is the type of objects which\n respect both schemas `A` and `B`.\n* With infix operator `|`: `A | B` is the type of objects which\n respect at least one of the schemas `A` or `B`. `&` takes precedence\n over `|`, i.e. `A & B | C & D` is to be read as `(A&B) | (C&D)`.\n* With conditional expressions: `if A then B elif C then D else E`\n will enforce constraint `B` if constraint `A` is met, enforce `D` if\n `C` is met, or enforce `E` if neither `A` nor `C` are met. `elif`\n and `else` parts are optional. For instance, `if {country: \"USA\"}\n then {postcode: r\"\\d{5}(-\\d{4})?\"} else {postcode: string}` will\n only check the postcode with the regex if the country is `\"USA\"`.\n* Parentheses can be added to enforce precedences , e.g. `A & (B|C) & D`\n* There is also an `not` operator: `{foo: not boolean}`.\n\n### From Python\n\nCombinations can also be performed on Python objects, e.g. the\nfollowing Python expression is OK: `Schema(\"{foo: number}\") |\nSchema(\"{bar: number}\")`, and produces a schema equivalent to\n`Schema(\"{foo: number}|{bar: number}\")`. When definitions are merged\nin Python with `|` or `&`, their definitions are merged as needed. If\na definition appears on both sides, it must be equal, i.e. one can\nmerge `{foo: <n>} where n=number` with `{bar: <n>} where n=number` but\nnot with `{foo: <n>} where n=integer`.\n\nMore formally\n-------------\n\n schema ::= type (\u00abwhere\u00bb definitions)?\n\n definitions ::= identifier \u00ab=\u00bb type (\u00aband\u00bb identifier \u00ab=\u00bb type)*\n\n type ::= type \u00ab&\u00bb type # allOf those types; takes precedence over \u00ab|\u00bb.\n | type \u00ab|\u00bb type # anyOf those types.\n | \u00ab(\u00bb type \u00ab)\u00bb # parentheses to enforce precedence.\n | \u00abnot\u00bb type # anything but this type.\n | \u00ab`\u00bbjson_litteral\u00ab`\u00bb # just this JSON constant value.\n | \u00ab<\u00bbidentifier\u00ab>\u00bb # identifier refering to the matching top-level definition.\n | r\"regular_expression\" # String matched by this regex.\n | f\"format\" # JSON Schema draft7 string format.\n | \u00abstring\u00bb cardinal? # a string, with this cardinal constraint on number of chars.\n | \u00abinteger\u00bb cardinal? # an integer within the range described by cardinal.\n | \u00abinteger\u00bb \u00ab/\u00bb int # an integer which must be multiple of that int.\n | \u00abobject\u00bb # any object.\n | \u00abarray\u00bb # any array.\n | \u00abboolean\u00bb # any boolean.\n | \u00abnull\u00bb # the null value.\n | \u00abnumber\u00bb # any number.\n | \u00abforbidden\u00bb # empty type (used mostly to disallow a property name).\n | object # structurally described object.\n | array # structurally described array.\n | conditional # conditional if/then/else rule\n\n cardinal ::= \u00ab{\u00bb int \u00ab}\u00bb # Exactly that number of chars / items / properties.\n | \u00ab{\u00bb \u00ab_\u00bb, int \u00ab}\u00bb # At most that number of chars / items / properties.\n | \u00ab{\u00bb int, \u00ab_\u00bb \u00ab}\u00bb # At least that number of chars / items / properties.\n | \u00ab{\u00bb int, int \u00ab}\u00bb # A number of chars / items / properties within this range.\n\n\n object ::= \u00ab{\u00bb object_restriction? (object_key \u00ab?\u00bb? \u00ab:\u00bb type \u00ab,\u00bb...)* \u00ab}\u00bb cardinal?\n # if \u00abonly\u00bb occurs without a regex, no extra property is allowed.\n # if \u00abonly\u00bb occurs with a regex, all extra property names must match that regex.\n # if \u00ab?\u00bb occurs, the preceding property is optional, otherwise it's required.\n\n object_restriction ::= \u00f8\n # Only explicitly listed property names are accepted:\n | \u00abonly\u00bb\n # every property name must conform to regex/reference:\n | \u00abonly\u00bb (r\"regex\" | \u00ab<\u00bbidentifier\u00ab>\u00bb)\n # non-listed property names must conform to regex, values to type:\n | \u00abonly\u00bb (r\"regex\" | \u00ab<\u00bbidentifier\u00ab>\u00bb | \u00ab_\u00bb)\u00ab:\u00bb type\n\n object_key ::= identifier # Litteral property name.\n | \u00ab\"\u00bbstring\u00ab\"\u00bb # Properties which aren't identifiers must be quoted.\n\n array ::= \u00ab[\u00bb \u00abonly\u00bb? \u00abunique\u00bb? (type \u00ab,\u00bb)* (\u00ab*\u00bb|\u00ab+\u00bb|\u00f8) \u00ab]\u00bb cardinal?\n # if \u00abonly\u00bb occurs, no extra item is allowed.\n # if \u00abunique\u00bb occurs, each array item must be different from every other.\n # if \u00ab*\u00bb occurs, the last type can be repeated from 0 to any times.\n # Every extra item must be of that type.\n # if \u00ab+\u00bb occurs, the last type can be repeated from 1 to any times.\n # Every extra item must be of that type.\n\n conditional ::= \u00abif\u00bb type \u00abthen\u00bb type (\u00abelif\u00bb type \u00abthen\u00bb type)* (\u00abelse\u00bb type)?\n\n int ::= /0x[0-9a-fA-F]+/ | /[0-9]+/\n identifier ::= /[A-Za-z_][A-Za-z_0-9]*/\n\n\n\nTODO\n----\n\nSome things that may be added in future versions:\n\n* on numbers:\n * ranges over floats (reusing cardinal grammar with float\n boundaries)\n * modulus constraints on floats `number/0.25`.\n * exclusive ranges in addition to inclusive ones. May use returned\n braces, e.g. `integer{0,0x100{` as an equivalent for\n `integer{0,0xFF}`?\n * ranges alone are treated as integer ranges, i.e. `{1, 5}` is a\n shortcut for `integer{1, 5}`? Not sure whether it enhances\n readability, and there would be a need for float support in\n ranges then.\n* combine string constraints: regex, format, cardinals... This can\n already be achieved with operator `&`.\n* try to embedded `#`-comments as `\"$comment\"`\n* Implementation:\n * bubble up `?` markers in grammar to the top level.\n* Syntax sugar:\n * optional marker: `foobar?` is equivalent to `foobar|null`. Not\n sure whether it's worth it, the difference between a missing\n field and a field holding `null` is most commonly not significant.\n * check that references as `propertyNames` indeed point at string\n types.\n * make keyword case-insensitive?\n * treat `{foo: forbidden}` as `{foo?: forbidden}` as it's the only\n thing that would make sense?\n* better error messages, on incorrect grammars, and on non-validating\n JSON data.\n* reciprocal feature: try and translate a JSON Schema into a shorter\n and more readable JSCN source.\n\nUsage\n-----\n\n### From command line\n\n $ echo -n '[integer*]' | jscn -\n { \"type\": \"array\",\n \"items\": {\"type\": \"integer\"},\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n }\n\n $ jscn --help\n\n usage: jscn [-h] [-o OUTPUT] [-v] [--version] [filename]\n\n Convert from a compact DSL into full JSON Schema.\n\n positional arguments:\n filename Input file; use '-' to read from stdin.\n\n optional arguments:\n -h, --help show this help message and exit\n -o OUTPUT, --output OUTPUT\n Output file; defaults to stdout\n -v, --verbose Verbose output\n --version Display version and exit\n\n### From Python API\n\nPython's `jsonschema_cn` package exports two main constructors:\n\n* `Schema()`, which compiles a source string into a schema object;\n* `Definitions()`, which compiles a source string (a sequence of\n definitions separated by keyword `and`, as in rule `definitions` of\n the formal grammar.\n\nSchema objects have a `jsonschema` property, which contains the Python\ndict of the corresponding JSON Schema.\n\nSchemas can be combined with Python operators `&` (`\"allOf\"`) and `|`\n(`\"anyOf\"`). When they have definitions, those definition sets are\nmerged, and definition names must not overlap.\n\nSchemas can also be combined with definitions through `|`, and\ndefinitions can be combined together also with `|`.\n\n >>> from jsonschema import Schema, Definitions\n\n >>> defs = Definitions(\"\"\"\n >>> id = r\"[a-z]+\" and\n >>> byte = integer{0,0xff}\n >>> \"\"\")\n\n >>> s = Schema(\"{only <id>: <byte>}\") | defs\n >>> s.jsonschema\n ValueError: Missing definition for byte\n\n >>> s = s | defs\n >>> s.jsonschema\n {\"$schema\": \"http://json-schema.org/draft-07/schema#\"\n \"type\": \"object\",\n \"propertyNames\": {\"$ref\": \"#/definitions/id\"},\n \"additionalProperties\": {\"$ref\": \"#/definitions/byte\"},\n \"definitions\": {\n \"id\": {\"type\": \"string\", \"pattern\": \"[a-z]+\"},\n \"byte\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255}\n }\n }\n\n >>> Schema(\"[integer, boolean+]{4}\").jsonschema\n { \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"array\",\n \"minItems\": 4, \"maxItems\": 4,\n \"items\": [{\"type\": \"integer\"}],\n \"additionalItems\": {\"type\": \"boolean\"},\n }\n\nSee also\n--------\n\nIf you spend a lot of time dealing with complex JSON data structures,\nyou might also want to try [jsview](https://github.com/fab13n/jsview),\na smarter JSON formatter, which tries to effectively use both your\nscreen's width and height, by only inserting q carriage returns when\nit makes sense:\n\n $ cat >schema.cn <<EOF\n\n { only codes: [<byte>+], id: r\"[a-z]+\", issued: f\"date\"}\n where byte = integer{0, 0xFF}\n EOF\n\n $ jscn schema.cn\n\n {\"type\": \"object\", \"required\": [\"codes\", \"id\", \"issued\"], \"properties\": {\n \"codes\": {\"type\": \"array\", \"items\": [{\"$ref\": \"#/definitions/byte\"}], \"ad\n ditionalItems\": {\"$ref\": \"#/definitions/byte\"}}, \"id\": {\"type\": \"string\",\n \"pattern\": \"[a-z]+\"}, \"issued\": {\"type\": \"string\", \"format\": \"date\"}}, \"a\n dditionalProperties\": false, \"definitions\": {\"byte\": {\"type\": \"integer\",\n \"minimum\": 0, \"maximum\": 255}}, \"$schema\": \"http://json-schema.org/draft-\n 07/schema#\"}\n\n $ cat schema.cn | jscn - | jsview -\n\n {\n \"type\": \"object\",\n \"required\": [\"codes\", \"id\", \"issued\"],\n \"properties\": {\n \"codes\": {\n \"type\": \"array\",\n \"items\": [{\"$ref\": \"#/definitions/byte\"}],\n \"additionalItems\": {\"$ref\": \"#/definitions/byte\"}\n },\n \"id\": {\"type\": \"string\", \"pattern\": \"[a-z]+\"},\n \"issued\": {\"type\": \"string\", \"format\": \"date\"}\n },\n \"additionalProperties\": false,\n \"definitions\": {\"byte\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255}},\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n }\n",
"bugtrack_url": null,
"license": "BSD",
"summary": "Compact notation for JSON Schemas",
"version": "0.28",
"project_urls": {
"Homepage": "http://github.com/fab13n/jsonschema_cn"
},
"split_keywords": [
"dsl",
"json",
"schema",
"jsonschema"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "fec45096b92da3b5fc541901845876c2b589b84dc94e5f25443aacae1e2c8858",
"md5": "c5c1629d185894345fb1b0f63bd6b8dc",
"sha256": "52541e3427196f01fc8933abfdf1d03d7591b04cfa1445cf2720efd84b7a0bbe"
},
"downloads": -1,
"filename": "jsonschema_cn-0.28.tar.gz",
"has_sig": false,
"md5_digest": "c5c1629d185894345fb1b0f63bd6b8dc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 29166,
"upload_time": "2024-07-16T10:05:25",
"upload_time_iso_8601": "2024-07-16T10:05:25.973361Z",
"url": "https://files.pythonhosted.org/packages/fe/c4/5096b92da3b5fc541901845876c2b589b84dc94e5f25443aacae1e2c8858/jsonschema_cn-0.28.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-16 10:05:25",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "fab13n",
"github_project": "jsonschema_cn",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "jsonschema-cn"
}