Skip to content

JSON Schema for R

corvusjsonschema is the Corvus JSON Schema evaluator for R: draft 4, 6, 7, 2019-09 and 2020-12. It is an R package over the Rust crate, and gives the same results and annotations as every other Corvus implementation.

  • Conformant. Passes the whole JSON-Schema-Test-Suite (required, optional and optional/format, every draft) except draft4/optional/zeroTerminatedFloats.json, as JSON text and read in place from R values.
  • Fast. R lists and vectors are read in place, with nothing converted or copied, and JSON text is validated without creating R values for it.
  • Results and annotations. Basic, detailed and verbose results, and annotations, as data frames.
  • No R dependencies. The package needs R 4.2 or later and nothing else at run time.

Install

install.packages("corvusjsonschema", repos = c("https://corvus-dotnet.r-universe.dev", "https://cloud.r-project.org"))

R-universe serves binary packages for Windows and macOS. On Linux the package is built from source, which needs a Rust toolchain (cargo and rustc, 1.89 or later).

Validate

library(corvusjsonschema)

validator <- compile_schema('{
  "type": "object",
  "required": ["id"],
  "properties": {"id": {"type": "integer", "minimum": 1}}
}')

is_valid(validator, list(id = 3))                     # TRUE: a list, read in place
is_valid_json(validator, '{"id": 0}')                 # FALSE: JSON text, parsed in Rust
is_valid_json(validator, c('{"id": 1}', '{}', NA))    # TRUE FALSE NA: one verdict for each text

compile_schema takes the schema as JSON text or as R values (a list, or TRUE or FALSE). Compile a schema once and keep the validator: compiling does the work that makes validating fast. is_valid_json takes a character vector and gives a verdict for each element, so a column of JSON documents is validated in one call.

Options

Option Default Meaning
default_dialect "draft2020-12" The dialect of a schema without $schema: "draft4", "draft6", "draft7", "draft2019-09" or "draft2020-12".
assert_format NA Whether format is asserted; NA follows the schema's vocabularies.
assert_format_in_legacy_drafts FALSE Assert format in drafts 4 to 7 when assert_format is NA.
assert_content TRUE Assert contentEncoding and contentMediaType in draft 7.
formats NULL A named list of functions, each taking a string and returning whether it is valid for that format.
resolver NULL A function from an absolute URI to the document (JSON text or R values), or NULL when it has none.
base_uri NULL The schema's base URI, when it has no $id.
entry_point NULL A subschema to validate against, such as "#/$defs/item".
max_depth 128 The deepest the evaluator recurses in place before it raises corvus_depth_error.
pattern_match_limit 1 The seconds one match of a regular expression against one string may take before the validation raises corvus_pattern_match_limit_error. NULL or 0 turns the limit off.

Failures are conditions that inherit from corvus_json_schema_error: corvus_compilation_error, corvus_pattern_too_large_error (which is a corvus_compilation_error), corvus_invalid_json_error, corvus_value_error, corvus_depth_error, corvus_pattern_match_limit_error and corvus_callback_error.

A schema and an instance may both come from outside, so a regular expression is bounded in what it costs. A match of a pattern or patternProperties regular expression that takes longer than pattern_match_limit is abandoned, and is_valid, is_valid_json, evaluate and evaluate_json raise corvus_pattern_match_limit_error: the instance is then neither valid nor not valid. A schema with a regular expression longer than 1,048,576 bytes, nested more than 1,000 groups deep or of more than about 16,000 terms does not compile, and compile_schema raises corvus_pattern_too_large_error.

Results and annotations

result <- evaluate(validator, list(id = "three"))
result$valid                                           # FALSE
result$results[, c("instance_location", "message")]
#>   instance_location                                        message
#> 1               /id The value was expected to match the subschema.
#> 2               /id The value was expected to be of type 'integer'
#> 3                   The value was expected to match the subschema.

titled <- compile_schema('{"title": "Person", "properties": {"name": {"title": "Name"}}}')
evaluate(titled, list(name = "Ada"), level = "verbose")$annotations
#>   instance_location keyword   schema_location  value
#> 1             /name   title #/properties/name   Name
#> 2                     title                 # Person

evaluate and evaluate_json take a level. "basic" records the failures without messages, "detailed" (the default) adds the messages, and "verbose" records every keyword, passing ones and annotations included. The results are a data frame with the columns valid, message, evaluation_location, schema_location and instance_location. The annotations are a data frame too, with the columns instance_location, keyword, schema_location and value, where the other implementations nest them in that order.

Values

An R value reads as JSON the way jsonlite's fromJSON(simplifyVector = FALSE) gives JSON values. NULL is null. A list with names is an object and a list without is an array, so list() is [] and an empty named list is {}. A logical, integer, double or character vector of length one is a scalar, and of any other length an array of scalars. Wrap a vector of length one in I() for an array of one element. NA of any type is null. A whole double, such as 3, is an integer as well as a number.

Values are read only as the schema examines them. A value the schema does examine that is not JSON raises corvus_value_error: NaN and the infinities, a function, an environment, and any object with a class, such as a data frame, a factor or a date.

Performance

Measured with the jsonschema-benchmark protocol and its 37 corpora, on one machine (containers pinned to eight cores, the median of three runs, the geometric mean over the corpora). Each figure is the package's time as a multiple of the other's, so below 1 is faster.

Against Validating R values, warm Validating JSON text, warm JSON text, parsing included
Blaze (C++) 5.9 3.2 0.18 (faster on 37 of 37)
The Corvus Ruby gem 1.11 0.61
  • R values are validated with one is_valid call for each document, on values that jsonlite parsed. A call costs about half a microsecond in R before the evaluator runs, which is most of the time for a small document. On large documents the package is faster than the Ruby gem.
  • JSON text is validated with one is_valid_json call for all the documents of a corpus, which is how a column of JSON documents is best validated in R. Blaze's time in that column is for documents it has already parsed. With parsing counted on both sides (the last column), the package takes 0.18 of Blaze's time.