Repository navigation
Releases
schema-calc v0.6.0
Why a record fails. A rejected record now comes with the constraint it breaks: validate.py prints it under REJECT, with nothing installed but jsonschema, and the playground and the JavaScript API give the same report. A contract can name its constraints, and the EN 16931 schemas name theirs after the rules they compile, so each rejected invoice's report names exactly the BR-CO rules the official Schematron finds broken. Try it in any browser; nothing is sent to a server.
What's new
-
Failure reports. The compiler marks each constraint's subschema with its source in
$comment, andexamples/validate.pyfinds those marks from Pythonjsonschema's own errors:REJECT invoice.invalid.json fails: total = subtotal + shipping - discountA constraint on each line lists the lines that break it:
fails: lines.amount = lines.net + lines.tax at /lines/0. When the record's format is wrong its arithmetic means nothing, so the report lists the format problems instead, at JSON Pointers:missing: /total,unexpected: /note,invalid: /lines/2/net. The verdict never depends on the report, and only a rejected record needs one; it costs about one more validation. -
Named constraints. A contract entry may be
{"name": "BR-CO-13", "constraint": "…"}, and the report printsfails BR-CO-13: …. The name goes into the constraint'stitle, changes no verdict and costs a few bytes. Names have 1 to 64 printable characters, without spaces at either end. -
The playground explains its verdict. The status line names the failing constraint, as in "Fails: total = subtotal + shipping - discount.", and the expected output it prints for the downloaded files includes the report, word for word as
validate.pyprints it.
EN 16931
The EN 16931 contracts name their constraints BR-CO-10 to BR-CO-17, and the comparison now checks reports as well as verdicts. On all 804 test invoices the verdicts agree with the official Schematron, as in 0.5.0, and on each of the 645 it rejects, the report names exactly the rules the Schematron reports broken, in Ajv and in validate.py.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.6.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
fails: total = subtotal + shipping - discount
The line-items schema and records work the same way with lines.schema.json, lines.valid.json and lines.invalid.json; the report names the line: fails: lines.amount = lines.net + lines.tax at /lines/0.
How the reports are checked
- Every report in the tests is compared with schemas compiled from one constraint at a time, including schemas whose constraints the resource packer moved into root
$defs, the$defslayout and the tally encoding. - On every rejected record of the two test corpora, 55,605 of them,
validate.pygives the same report as Ajv.npm run check:pythonandcheck:tallyrecheck an evenly spaced sample of 12,868 on each run. - The test corpus is unchanged at 45,451 records across 92 schemas, and in the twelve-validator compatibility run on this release's code, again none gave a wrong answer.
For library users
prepareValidator(schema)returns a validator with afailures(record)method:{format: [{problem, at}], constraints: []}when the format is wrong, else{format: [], constraints: [{source, name?, list?, items?}]}.describeFailures(report), fromsrc/failures.mjs, prints it asvalidate.pydoes.normalizeSpecaccepts a constraint as{name, constraint}and returnsconstraintsas strings, plusnames, withnullfor an unnamed one, when any is named.- Compiled output changes only for a named constraint, which gains a
title. The shipped example schemas are byte-for-byte the same as in 0.5.0;validate.pyis the file that changed.
Files
invoice.schema.json is the compiled invoice contract, unchanged from 0.5.0; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.6.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.6.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Unchanged from 0.5.0: sums and differences of decimal strings, signed if declared, their whole-number multiples, abs(), products by a field with listed values, and sums over lists, related by =, <=, >=, < or > and joined by and and or with presence tests, on the record or on each line: up to 16 fields, 4 lists of 20 items, 8 constraints, 12 digits and 32 listed values per field. No other products of fields, division, !=, parentheses, not, indexing into a list or other nested objects. A research prototype; it is not published to npm.
schema-calc v0.5.0
A product by a listed rate. A field whose values a contract lists can now multiply another field, abs() drops a sign, and the lines of a list can leave fields out and join their own relations with and and or. With these, BR-CO-17 of EN 16931, each VAT category's tax as its taxable amount times its rate, compiles into plain JSON Schema beside the seven document-total rules of 0.4.0, and the schemas give the official Schematron's verdict on every one of 804 test invoices, rule by rule. Try it in any browser; nothing is sent to a server.
What's new
- Products by a field with listed values.
"values": {"rate": ["0", "7", "19"]}lists the values a field may take, on the record or in a list's declaration for its items' fields. The field then accepts only those, each in its one valid spelling, and may multiply another field:100 * tax <= rate * base. The compiler builds the relation once for each value, with the value as a coefficient, and ananyOfchooses among them, so the schema is about the sum of their sizes: 68 kB for that relation at four integer digits. One such field per relation, multiplying a field rather than a sum, with up to 32 values. Products of two free fields remain out of reach. abs(x), an amount without its sign, for one field or one line's field. It leaves out the sign tests that a signed amount makes at every digit, so it costs less thanx:abs(a) <= bis 17 kB,a <= b30 kB.- Coefficients ending in zeros cost nothing. A coefficient c × 10ᵏ now reads its field k columns later with coefficient c.
total = k * price + shippingcompiles to 18, 16 and 14 kB withk= 10, 100 and 1,000, against 80, 401 and 751 kB in 0.4.0. - Optional fields on a line.
"lines": {"maxItems": 10, "fields": ["net", "tax", "rate"], "optional": ["rate"]}lets each line leaverateout; in arithmetic an absent field counts as zero, as on the record. and,orandhason each line. A constraint on each line can join relations and presence tests on that line:has lines.rate and lines.tax <= lines.net or no lines.rate and lines.tax = 0.
EN 16931: BR-CO-17
The EN 16931 page now compiles BR-CO-10 to BR-CO-17. In both Schematrons, BR-CO-17 leaves a tolerance of one currency unit, not one cent: the tax amount, without its sign, must come within a unit of the taxable amount, without its sign, times the rate, rounded to the cent, strictly in UBL and inclusively in CII. Rounding moves the bounds by only half a cent, so the rule is linear for each rate: −99.5 ≤ rate × |taxable| − 100 × |tax| < 99.5 in UBL, and the same with 100.5 in CII. A category without a rate, or at zero, must have a tax that rounds to zero. The contracts list the ten rates of the official examples, 0, 6, 10, 12, 15, 19, 21, 22, 25 and 27; a category at any other rate fails the schema, and a contract for real invoices lists the rates its sellers charge.
On the 34 official examples and 770 copies with an amount changed or an optional element removed, all 804 verdicts agree, rule by rule, in Ajv and in Python jsonschema with its default options. Five new changes to each invoice with a VAT category test BR-CO-17 alone: the first category's tax moved by ±1.00 or ±0.50, with the totals moved with it, and its rate removed. A whole unit is where the syntaxes part: UBL rejects all 38 such copies, CII 5 of 26. Each schema is 10.5 MB, most of it still for adding up 20 signed lines; BR-CO-17 alone takes 874 kB, about 95 kB per nonzero rate. Ajv prepares one in under 20 seconds and checks an invoice in under a millisecond; Python jsonschema takes about a second per invoice.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.5.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
The line-items schema and records work the same way with lines.schema.json, lines.valid.json and lines.invalid.json.
Validator compatibility
The flat test corpus grew from 43,319 to 45,451 records across 92 schemas, adding abs(), coefficients ending in zeros, optional fields of a line with and, or and has on each line, and products by fields with listed values, including fractional and negative values, against an independent oracle. All of them pass Ajv and Python jsonschema, and twelve validators in five languages ran them (table). None gave a wrong answer. Nine load every schema. @cfworker/json-schema needs --defs, as before: it refuses the 80 flat schemas that nest one embedded resource in another. @exodus/schemasafe times out loading the 4 largest, from 364 to 410 kB, and with --defs now loads every schema; the coefficient schemas that timed out in 0.4.0 are now 31 and 37 kB. jsonschema-rs runs out of memory on 53 (51 with --defs), including all five shipped ones.
For library users
- Compiled output changed in two places, with the same verdicts. Presence tests now state
type: "object", which Ajv's strict mode asks for:has paidis{type: "object", required: ["paid"], properties: {paid: true}}. And a coefficient ending in zeros compiles to the smaller schema above. The shipped example schemas are byte-for-byte the same as in 0.4.0. - A relation with a product comes from
parseConstraintwithby, the field with listed values, andproducts, the terms it multiplies;instantiate(relation, units)returns the plain relation for one value, given in the smallest unit (1900 for 19.00 at scale two). Such a relation counts in units of 10^(-2 × scale), so its other coefficients and its constant are scaled by 10^scale. - A term may carry a third element,
true, when it is read withabs():["a", 1, true]. normalizeSpecreturnsvalueswhen a field lists them, and a list's declaration returns its ownoptionalandvalues.formatAmount(units, scale)writes an amount in its one valid spelling.- The tally encoding refuses listed values and
abs(), with an error that names the flat encoding.
Files
invoice.schema.json is the compiled invoice contract, unchanged from 0.4.0; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.5.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.5.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Sums and differences of decimal strings, signed if declared, their whole-number multiples, abs(), products by a field with listed values, and sums over lists, related by =, <=, >=, < or > and joined by and and or with presence tests, on the record or on each line: up to 16 fields, 4 lists of 20 items, 8 constraints, 12 digits and 32 listed values per field. No other products of fields, division, !=, parentheses, not, indexing into a list or other nested objects. The tally encoding takes no lists, signed amounts, optional fields, listed values, abs() or joined relations. A research prototype; it is not published to npm.
schema-calc v0.4.0
Invoice rules. A contract can now check every line of a list, use negative amounts and leave fields out, and join relations with and and or. With these, seven of the document-total rules of EN 16931, the European e-invoicing standard, compile into plain JSON Schema that gives the official Schematron's verdict on every one of 644 test invoices. Try it in any browser; nothing is sent to a server.
What's new
- Constraints on each line. A constraint that reads
lines.fieldoutsidesum()holds on every line:lines.amount = lines.net + lines.tax. It compiles once and is applied to every item, so its size does not depend on the list's length: on its own it is 20.5 kB for 4 lines or for 20. It reads one line's fields and constants only. - Signed amounts.
"signed": truelets every amount carry one leading minus sign, as credit notes and allowances need. A signed schema is about four times larger: 3.6–4.7 times for the shipped examples. - Optional fields.
"optional": ["paid"]lists fields that may be absent; in arithmetic an absent field counts as zero. and,or,hasandno. Relations can be joined, withandbinding first, andhas paidorno linestests whether an optional field is present or a list has an item:has paid and due = total - paid or no paid and due = total. Up to eight relations and tests per constraint. A constraint that is one relation compiles exactly as before.- Smaller, shallower list sums. A list's sum now reads, in each column, how many lines have each digit, with
contains,minContainsandmaxContains, instead of reading every line in turn.sum(lines) = subtotalat four integer digits is 16, 72, 240 and 682 kB for 1, 4, 10 and 20 lines, against 9, 72, 386 and 1,437 kB in 0.3.0, and a validator's decision path grows with the number of counts rather than lines.compile(spec, { listSums: 'positions' })keeps the old construction. - Faster in Python. The root of every schema is now one reference,
$ref: "_", to its body in$defs, so Pythonjsonschemacrawls the document once per validation instead of once per constraint: the flat test corpus checks in 142 s instead of 493, and the larger test schemas 2.5 to 9 times faster. It costs 27 bytes: the invoice schema is 41,391 B. - Larger contracts. Up to 16 fields, from 8, and 4 lists, from 2.
- The line-items example checks each line.
lines.amount = lines.net + lines.taxandsum(lines.amount) = total: 16.66 + 3.33 = 19.99, 4.13 + 0.82 = 4.95 and 0.05 + 0.01 = 0.06, adding up to 25.00. Add one cent to a tax makes the first line 16.66 + 3.34, which the schema rejects although the total still adds up. It is 95,921 B.
EN 16931 totals in plain JSON Schema
A new page compiles BR-CO-10 to BR-CO-16 (line, allowance, charge and VAT sums; the totals without and with VAT; the amount due) and compares the result with the official Schematron, release validation-1.3.17. The two official syntaxes, UBL and CII, check some rules with different conditions on missing amounts, so each has its own contract, stating every rule as its Schematron checks it. On the 34 official examples and 610 copies with an amount raised by one cent or an optional element removed, all 644 verdicts agree, rule by rule, in Ajv and in Python jsonschema with its default options. Each schema is 9.6 MB, most of it for adding up 20 signed lines. Products, such as BR-CO-17's tax amount equal to taxable amount times rate, are not linear and remain out of reach.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.4.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
The line-items schema and records work the same way with lines.schema.json, lines.valid.json and lines.invalid.json.
Validator compatibility
The flat test corpus grew from 34,732 to 43,319 records across 85 schemas, adding constraints on each line, every one-digit signed sum, malformed signs, absent and explicit optional fields, signed lists, and relations joined by and and or against an independent oracle. All of them pass Ajv and Python jsonschema, and twelve validators in five languages ran them (table). None gave a wrong answer. Nine load every schema. @cfworker/json-schema needs --defs, as before: it refuses the 73 flat schemas that nest one embedded resource in another. @exodus/schemasafe times out loading 3 of the larger ones (1 with --defs). jsonschema-rs runs out of memory on 50 (46 with --defs), including all five shipped ones.
For library users
- The root is one reference. A compiled schema is
{ $schema, $id, title, $ref: "_", $defs: { …, _: body } }. Code that readschema.propertiesorschema.allOfreadsschema.$defs._instead. Verdicts are unchanged. and,or,hasandnocannot name a field, a list or a line's field. A contract that used one of them as a name no longer compiles; the error names the field to rename.- Ajv's strict mode no longer warns about list sums, which no longer use partial
prefixItemstuples. WithlistSums: 'positions'they do, as in 0.3.0. - Very large schemas need Ajv's resources added one at a time. On the 9.6 MB EN 16931 schemas, Ajv's own
compiletakes minutes and then overflows the stack while validating;prepareValidator, which the playground uses, compiles each resource on its own, bottom-up, and prepares them in well under a minute. parseRule(source, spec)returns a constraint's clauses: parsed relations, and{ has }or{ no }tests.parseConstraintstill parses one relation.normalizeSpecreturnssignedandoptionalwhen they are set.
Files
invoice.schema.json is the compiled invoice contract, 27 bytes larger than in 0.3.0 for the root reference, with the same verdicts; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.4.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.4.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Sums and differences of decimal strings, signed if declared, their whole-number multiples and sums over lists, related by =, <=, >=, < or > and joined by and and or with presence tests: up to 16 fields, 4 lists of 20 items, 8 constraints and 12 digits. No products of fields, division, !=, parentheses, not, indexing into a list or other nested objects. The tally encoding takes no lists, signed amounts, optional fields or joined relations. A research prototype; it is not published to npm.
schema-calc v0.3.0
Lists. A contract can now sum a list of amounts or of line items, so an ordinary validator can check that the lines of an invoice add up to its subtotal, however many lines there are up to a declared maximum. Contracts without lists compile exactly as in 0.2.0, and the four earlier examples are unchanged. Try it in any browser; nothing is sent to a server.
What's new
- Lists of amounts. Declare
"lists": {"lines": 10}and writesum(lines)anywhere a field can appear, with a coefficient if you like. A list may hold up to 20 amounts and be any length up to its maximum, including empty, which sums to zero. Each item must be an amount in the declared format. Up to two lists per contract. - Lists of objects.
"lines": {"maxItems": 10, "fields": ["net", "tax"]}declares line items with up to four amount fields, summed separately withsum(lines.net)andsum(lines.tax). Every line must carry each declared field; other properties, such as a description or SKU, pass through unchecked. Writingsum(lines)for a list of objects, orsum(lines.amount)for a list of amounts, is refused with a message naming the right form. - A fifth example, line items.
sum(lines.amount) = subtotal, for up to four lines with a description each: Notebook 19.99, Pens 4.95 and Stamp 0.06 add up to 25.00, and Add one cent turns the subtotal into 25.01, which the schema rejects. It is 77,917 B and has its own tab in the playground, which shows each line on one row. - Size grows roughly with the square of the length, because every position is another digit to add in each column:
sum(lines) = subtotal, with four integer and two fractional digits, is 9 kB for one line, 72 kB for 4, 386 kB for 10 and 1.4 MB for 20. Lines that are objects cost 3–17% more. - The tally encoding refuses lists, with a message pointing to the flat encoding: its columns have a fixed set of blocks, and a list's length varies.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.3.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
The line-items schema and records work the same way with lines.schema.json, lines.valid.json and lines.invalid.json.
Validator compatibility
The flat test corpus grew from 32,000 to 34,732 records, adding lists of every length up to their maximum, empty and malformed lists, lines with other properties and lines with separate net and tax fields. All of them pass Ajv and Python jsonschema, and twelve validators in five languages ran them (table). None gave a wrong answer. Nine load every schema. @cfworker/json-schema needs --defs, as before. @exodus/schemasafe times out loading some schemas from about 170 kB up, and which ones varies between runs: four of 75 locally, six on the slower Linux CI runner. jsonschema-rs runs out of memory on the larger flat schemas, including all five shipped ones.
For library users
A list position is read through a prefixItems tuple that stops at that position. Ajv's default strict mode logs a warning for every such partial tuple, and strict: true refuses it; new Ajv2020({ strictTuples: false }) keeps the other strict checks. No other tested validator objects. normalizeSpec returns the declared lists, and parseConstraint names each position as a term of its own, lines[0] or lines[0].amount. fields may be empty when the contract declares a list.
Files
invoice.schema.json is the compiled invoice contract, unchanged since 0.1.1; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.3.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.3.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Sums and differences of nonnegative decimal strings, their whole-number multiples and sums over lists, related by =, <=, >=, < or >: up to 8 fields, 2 lists of 20 items, 8 constraints and 12 digits. No products of fields, division, !=, indexing into a list, constraints on each line, other nested objects, or lists in the tally encoding yet. A research prototype; it is not published to npm.
schema-calc v0.2.0
Coefficients and inequalities. A contract can now multiply a field by a whole number and compare two sides with <=, >=, < or >, so an ordinary validator can check discount <= subtotal, or that each share is a third of the total rounded down to the cent. The shipped examples compile to byte-identical schemas; a 0.1.1 contract changes only if part of its schema held more than 2,000 subschemas without a reference. Try it in any browser; nothing is sent to a server.
What's new
- Whole-number coefficients, written
3 * priceorprice * 3, from 1 to 1,000. Products of fields and decimal coefficients are refused with a clear message. The schema grows with the coefficient:total = k * price + shippingwith four integer and two fractional digits is 21 kB atk= 1, 80 kB at 10, 401 kB at 100 and 751 kB at 1,000. - Inequalities.
>=and>negate both sides, and a strict comparison adds one minor unit, so every inequality becomessum <= 0. Where an equation rejects a column that leaves a remainder, an inequality carries the remainder rounded up instead (proof). Addingdiscount <= subtotalto the invoice takes it from 41,364 to 46,422 B; a bound such asshipping < 100is about 1 kB. - A fourth example, the even split.
3 * share <= totalandtotal < 3 * share + 0.03accept exactly the share rounded down to the cent: 33.33 of 100.00, but neither 33.32 nor 33.34. It is 26,728 B, or 12,278 B in the tally encoding, and has its own tab in the playground. - At most 2,000 subschemas between references. With a coefficient of 1,000, Ajv's call stack overflowed while validating some records; Ajv and schemasafe compile each such region into one function. Larger regions now move into
$defs, as the 32-level nesting limit already did.compile(spec, { maxSubschemas })sets another limit. No existing example changes. - The tally encoding refuses coefficients above 16, with a message pointing to the flat encoding. Its schema grows roughly with the cube of the coefficient:
total = 16 * price + shippingis 87 kB, and a coefficient of 100 would take megabytes.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.2.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
Validator compatibility
The test corpus grew to 32,000 flat and 31,521 tally records, adding coefficients from 2 to 1,000, exhaustive one-digit comparisons, signed inequalities one unit either side of the boundary, and the even split. All of them pass Ajv and Python jsonschema, and twelve validators in five languages ran them (table). None gave a wrong answer. Nine load every schema. @cfworker/json-schema needs --defs. @exodus/schemasafe times out loading the two largest test schemas, compiled from coefficients of 100 and 1,000 (333 kB and 911 kB); timed separately, it takes 159 s and 11 minutes. jsonschema-rs runs out of memory on the larger flat schemas, as before.
For library users
parseConstraint parses every relation. parseEquation still accepts only equations, because code written against 0.1 reads its result as one.
Files
invoice.schema.json is the compiled invoice contract, unchanged since 0.1.1; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.2.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.2.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Sums and differences of nonnegative decimal strings and their whole-number multiples, related by =, <=, >=, < or >: up to 8 fields, 8 constraints and 12 digits. No products of fields, division, !=, or nested objects and arrays yet. A research prototype; it is not published to npm.
schema-calc v0.1.1
A compatibility release. The compiled schemas now load with default options in ten of the twelve Draft 2020-12 validators tested in five languages, up from six. The input language is unchanged, and no validator gave a wrong answer on any record. The invoice schema grows by 132 bytes. Try it in any browser; nothing is sent to a server.
Validator compatibility
The flat corpus (31 schemas, 24,472 records) and the shipped examples were run through every validator with its default options (full table):
| Validator | v0.1.0 schemas | v0.1.1 schemas |
|---|---|---|
| santhosh-tekuri/jsonschema, kaptinlin/jsonschema (Go) | refuses all 31 | loads all, all records agree |
| JsonSchema.Net (C#) | refuses 3 | loads all, all records agree |
| networknt json-schema-validator (Java) | times out on the invoice | loads all, all records agree |
| @cfworker/json-schema (JS) | refuses 23 | refuses 23; loads all with --defs |
| jsonschema-rs (Python) | exceeds 2 GB on 11 | exceeds 2 GB on 10 (8 with --defs) |
| Ajv, hyperjump, schemasafe, Python jsonschema, harrel, json-sKema | loads all | loads all, all records agree |
What changed
- No lookahead. Each field's format ends with
$plusnot: {pattern: "[^.0-9]"}instead of(?![\s\S]), which Go's RE2 does not support. The second pattern is needed because$in Python, Java and .NET also matches before a final line terminator; tests now cover\n,\r,\u0085and. - At most 32 nesting levels. Subschemas that would nest deeper move into a root
$defs. JsonSchema.Net's JSON parser stops at 64 levels, and networknt's load time grows steeply with depth.compile(spec, { maxDepth })sets the limit. - Optional
$defslayout.--defsorcompile(spec, { layout: 'defs' })declares every shared resource in root$defs, so none sits inside another, which @cfworker/json-schema requires. It costs 17–20% more bytes. - Browser preparation now orders resources by dependency.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.1.1
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
Sizes
| Example | v0.1.0 | v0.1.1 |
|---|---|---|
| invoice | 41,232 B | 41,364 B |
| allocation | 16,315 B | 16,447 B |
| carry chain | 20,510 B | 20,605 B |
Files
invoice.schema.json is the compiled invoice contract; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off, unchanged from 0.1.0. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.1.1.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.1.1.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Unchanged: sums and differences of nonnegative decimal strings, up to 8 fields, 8 equations and 12 digits. A research prototype; it is not published to npm. Benchmark timings in the repository were measured on the 0.1.0 schemas.
schema-calc v0.1.0
The first release of Schema Calc: a compiler from bounded decimal equations to ordinary Draft 2020-12 JSON Schema, so a stock validator in any language can reject a one-cent error. Try it in any browser; nothing is sent to a server.
Check it without Schema Calc
Python 3 and jsonschema only, in an empty directory:
python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.1.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.jsonACCEPT invoice.valid.json
REJECT invoice.invalid.json
Results
| invoice contract | total = subtotal + shipping - discount, 4 integer and 2 fractional digits |
| invoice schema | 41,232 B, from 284,300 B for the first construction, same input and bounds |
| same contract, tally encoding | 3,569 B schema, but the record grows from 75 to 676 B |
| generated corpus, each checked in Ajv 8.20.0 and jsonschema 4.25.1 | 24,469 flat and 24,473 tally checks |
| playground handoff (CI) | browser downloads validated in a fresh venv: accepted and one-cent records, both encodings |
Smallest is not fastest: against the larger mixed-radix schema, the packed default validates an invoice in about 6.25 µs instead of 5.19 µs in Ajv, and 24.98 ms instead of 1.58 ms in Python (measurements).
Files
invoice.schema.json is the compiled invoice contract; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.1.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.1.0.zip.sha256 is its checksum and SHA256SUMS covers every file.
Scope
Sums and differences of nonnegative decimal strings: up to 8 fields, 8 equations and 12 digits. No multiplication, comparisons or nested objects yet. A research prototype; it is not published to npm. See the README for the computational model, the proof and the compression steps.
