JSONSchemer
JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1.
Installation
Add this line to your application's Gemfile:
gem 'json_schemer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install json_schemer
Usage
require 'json_schemer'
schema = {
'type' => 'object',
'properties' => {
'abc' => {
'type' => 'integer',
'minimum' => 11
}
}
}
schemer = JSONSchemer.schema(schema)
# true/false validation
schemer.valid?({ 'abc' => 11 })
# => true
schemer.valid?({ 'abc' => 10 })
# => false
# error validation (`validate` returns an enumerator)
schemer.validate({ 'abc' => 10 }).to_a
# => [{"data"=>10,
# "data_pointer"=>"/abc",
# "schema"=>{"type"=>"integer", "minimum"=>11},
# "schema_pointer"=>"/properties/abc",
# "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}},
# "type"=>"minimum",
# "error"=>"number at `/abc` is less than: 11"}]
# default property values
data = {}
JSONSchemer.schema(
{
'properties' => {
'foo' => {
'default' => 'bar'
}
}
},
insert_property_defaults: true
).valid?(data)
data
# => {"foo"=>"bar"}
# schema files
require 'pathname'
schema = Pathname.new('/path/to/schema.json')
schemer = JSONSchemer.schema(schema)
# schema json string
schema = '{ "type": "integer" }'
schemer = JSONSchemer.schema(schema)
# schema validation
JSONSchemer.valid_schema?({ '$id' => 'valid' })
# => true
JSONSchemer.validate_schema({ '$id' => '#invalid' }).to_a
# => [{"data"=>"#invalid",
# "data_pointer"=>"/$id",
# "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
# "schema_pointer"=>"/properties/$id",
# "root_schema"=>{...meta schema},
# "type"=>"pattern",
# "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
JSONSchemer.schema({ '$id' => 'valid' }).valid_schema?
# => true
JSONSchemer.schema({ '$id' => '#invalid' }).validate_schema.to_a
# => [{"data"=>"#invalid",
# "data_pointer"=>"/$id",
# "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
# "schema_pointer"=>"/properties/$id",
# "root_schema"=>{...meta schema},
# "type"=>"pattern",
# "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
# subschemas
schema = {
'type' => 'integer',
'$defs' => {
'foo' => {
'type' => 'string'
}
}
}
schemer = JSONSchemer.schema(schema)
schemer.ref('#/$defs/foo').validate(1).to_a
# => [{"data"=>1,
# "data_pointer"=>"",
# "schema"=>{"type"=>"string"},
# "schema_pointer"=>"/$defs/foo",
# "root_schema"=>{"type"=>"integer", "$defs"=>{"foo"=>{"type"=>"string"}}},
# "type"=>"string",
# "error"=>"instance at root is not a string"}]
# schema bundling (https://json-schema.org/draft/2020-12/json-schema-core.html#section-9.3)
schema = {
'$id' => 'http://example.com/schema',
'allOf' => [
{ '$ref' => 'schema/one' },
{ '$ref' => 'schema/two' }
]
}
refs = {
URI('http://example.com/schema/one') => {
'type' => 'integer'
},
URI('http://example.com/schema/two') => {
'minimum' => 11
}
}
schemer = JSONSchemer.schema(schema, :ref_resolver => refs.to_proc)
schemer.bundle
# => {"$id"=>"http://example.com/schema",
# "allOf"=>[{"$ref"=>"schema/one"}, {"$ref"=>"schema/two"}],
# "$schema"=>"https://json-schema.org/draft/2020-12/schema",
# "$defs"=>
# {"http://example.com/schema/one"=>{"type"=>"integer", "$id"=>"http://example.com/schema/one", "$schema"=>"https://json-schema.org/draft/2020-12/schema"},
# "http://example.com/schema/two"=>{"minimum"=>11, "$id"=>"http://example.com/schema/two", "$schema"=>"https://json-schema.org/draft/2020-12/schema"}}}
Options
JSONSchemer.schema(
schema,
# meta schema to use for vocabularies (keyword behavior) and schema validation
# String/JSONSchemer::Schema
# 'https://json-schema.org/draft/2020-12/schema': JSONSchemer.draft202012
# 'https://json-schema.org/draft/2019-09/schema': JSONSchemer.draft201909
# 'http://json-schema.org/draft-07/schema#': JSONSchemer.draft7
# 'http://json-schema.org/draft-06/schema#': JSONSchemer.draft6
# 'http://json-schema.org/draft-04/schema#': JSONSchemer.draft4
# 'http://json-schema.org/schema#': JSONSchemer.draft4
# 'https://spec.openapis.org/oas/3.1/dialect/base': JSONSchemer.openapi31
# 'json-schemer://openapi30/schema': JSONSchemer.openapi30
# default: JSONSchemer.draft202012
meta_schema: 'https://json-schema.org/draft/2020-12/schema',
# validate `format` (https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7)
# true/false
# default: true
format: true,
# insert default property values during validation
# true/false
# default: false
insert_property_defaults: true,
# modify properties during validation. You can pass one Proc or a list of Procs to modify data.
# Proc/[Proc]
# default: nil
before_property_validation: proc do |data, property, property_schema, _parent|
data[property] ||= 42
end,
# modify properties after validation. You can pass one Proc or a list of Procs to modify data.
# Proc/[Proc]
# default: nil
after_property_validation: proc do |data, property, property_schema, _parent|
data[property] = Date.iso8601(data[property]) if property_schema.is_a?(Hash) && property_schema['format'] == 'date'
end,
# resolve external references
# 'net/http'/proc/lambda/respond_to?(:call)
# 'net/http': proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
# default: proc { |uri| raise UnknownRef, uri.to_s }
ref_resolver: 'net/http',
# use different method to match regexes
# 'ruby'/'ecma'/proc/lambda/respond_to?(:call)
# 'ruby': proc { |pattern| Regexp.new(pattern) }
# default: 'ruby'
regexp_resolver: proc do |pattern|
RE2::Regexp.new(pattern)
end,
# output formatting (https://json-schema.org/draft/2020-12/json-schema-core.html#section-12)
# 'classic'/'flag'/'basic'/'detailed'/'verbose'
# default: 'classic'
output_format: 'basic',
# validate `readOnly`/`writeOnly` keywords (https://spec.openapis.org/oas/v3.0.3#fixed-fields-19)
# 'read'/'write'/nil
# default: nil
access_mode: 'read'
)
OpenAPI
document = JSONSchemer.openapi({
'openapi' => '3.1.0',
'info' => {
'title' => 'example'
},
'components' => {
'schemas' => {
'example' => {
'type' => 'integer'
}
}
}
})
# document validation using meta schema
document.valid?
# => false
document.validate.to_a
# => [{"data"=>{"title"=>"example"},
# "data_pointer"=>"/info",
# "schema"=>{...info schema},
# "schema_pointer"=>"/$defs/info",
# "root_schema"=>{...meta schema},
# "type"=>"required",
# "details"=>{"missing_keys"=>["version"]}},
# ...]
# data validation using schema by name (in `components/schemas`)
document.schema('example').valid?(1)
# => true
document.schema('example').valid?('one')
# => false
# data validation using schema by ref
document.ref('#/components/schemas/example').valid?(1)
# => true
document.ref('#/components/schemas/example').valid?('one')
# => false
CLI
The json_schemer
executable takes a JSON schema file as the first argument followed by one or more JSON data files to validate. If there are any validation errors, it outputs them and returns an error code.
Validation errors are output as single-line JSON objects. The --errors
option can be used to limit the number of errors returned or prevent output entirely (and fail fast).
The schema or data can also be read from stdin using -
.
% json_schemer --help
Usage:
json_schemer [options] <schema> <data>...
json_schemer [options] <schema> -
json_schemer [options] - <data>...
json_schemer -h | --help
json_schemer --version
Options:
-e, --errors MAX Maximum number of errors to output
Use "0" to validate with no output
-h, --help Show help
-v, --version Show version
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
Build Status
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/davishmcclurg/json_schemer.
License
The gem is available as open source under the terms of the MIT License.