jsonschema
Comprehensive JSON Schema validation and generator for Crystal. Read below for a high-level description of the API, or check out the API Docs for full details.
Installation
-
Add the dependency to your
shard.yml
:dependencies: jsonschema: github: aarongodin/jsonschema
-
Run
shards install
Overview
- Read JSON files from your file system at compile time and generate Crystal code to convert a JSON Schema document to a
JSONSchema::Validator
object. - Create
JSONSchema::Validator
instances during runtime by providing JSON input toJSONSchema.from_json()
- Build
JSONSchema::Validator
objects using a fluent API directly in your application code.
Usage
Generating Validators
Generate code using the provided macros which output a reference to a JSONSchema::Validator
object. You can assign the value to a variable or use it any place an expression can be used.
require "jsonschema"
validator = JSONSchema.create_validator "my_schema.json"
You can use create_validator_method
to define a method that returns the reference to the JSONSchema::Validator
.
class RequestBody
JSONSchema.create_validator_method "my_schema.json"
end
This is syntactically equivalent to:
class RequestBody
def validator : JSONSchema::Validator
JSONSchema.create_validator "my_schema.json"
end
end
The create_validator_method
macro allows you to also customize the method name that is generated by passing a second argument:
class ExampleRoute
JSONSchema.create_validator_method "request_schema.json", "request_body_validator"
JSONSchema.create_validator_method "response_schema.json", "response_body_validator"
end
r = ExampleRoute.new
r.request_body_validator # => #<JSONSchema::ObjectValidator:...
r.response_body_validator # => #<JSONSchema::ObjectValidator:...
Omitting extension: You can omit the file extension in any of the macros, in which case a file with
.json
as the extension will be loaded. ex:JSONSchema.create_validator "my_schema" # => Loads "my_schema.json"
Validating JSON
Use the #validate
method on any generated validator to receive a JSONSchema::ValidationResult
:
require "json"
require "jsonschema"
class RequestBody
JSONSchema.create_validator_method "my_schema.json"
end
request_body = RequestBody.new
input_json = JSON.parse(%{{"test": "example"}})
request_body.validator.validate(input_json) # => JSONSchema::ValidationResult(@status=:success, @errors=[])
The JSONSchema::ValidationResult
will contain either :success
or :error
as the status
. On :error
, you can check the @errors
array for a list of JSONSchema::ValidationError
and respond in your code accordingly.
Context
When a ValidationError
is created, it includes a NodeContext
object which represents the location where the error occurred. You can get the value from this location by using NodeContext#dig_into
:
result = validator.validate(input_json)
result.errors[0].context.dig_into(input_json) # => returns JSON::Any() that wraps value at that location
You can get a string path with NodeContext#to_s
similar to what jq
(ref) uses for pathing.
result = validator.validate(input_json)
result.errors[0].context.to_s # => String such as ".person.name" or ".example[2].title"
Create From JSON Input
Use the #from_json
method to create JSONSchema::Validator
objects from any parsed JSON. This is a runtime method and can raise an exception for any invalid schema. To return nil
instead of raising, use #from_json?
.
validator = JSONSchema.from_json(JSON.parse(
<<-JSON
{
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
JSON
))
validator.validate(JSON.parse("...")) # => JSONSchema::ValidationResult(@status=:success, @errors=[])
Create From Fluent API
The fluent API is a DSL with a concise syntax for generating JSONSchema::Validator
objects. See the Fluent class for full usage specification. Here's an example that shows a complex schema represented using the fluent API:
require "jsonschema"
js = JSONSchema.fluent
validator = js.object do
prop "first_name", (js.string do
min_length 2
max_length 64
end)
prop "last_name", (js.string do
min_length 2
max_length 64
end)
prop "email", js.string { format "email" }
prop "address", (js.object do
prop "street", js.string
prop "city", js.string
prop "state", js.string
prop "zipcode", js.string
end)
prop "nicknames", (js.array do
min_items 1
items js.string
end)
end
Serialize
You can serialize a validator to its string representation through the standard #to_json
methods:
js = JSONSchema.fluent
validator = js.generic do
any_of(
js.string,
js.number
)
end
puts validator.to_json # {"anyOf":[{"type":"string"},{"type":"number"}]}
json-schema Features
Core Types
All JSON Schema types are supported!
string
number
andinteger
array
object
null
boolean
The generic keywords const
and enum
are also supported.
enum
can be provided either as a generic keyword (schema without atype
value), or on a typed schema.const
may only be provided as a generic keyword.
Composite Schema
Composite schemas using not
, anyOf
, allOf
, or oneOf
are supported! These can be used on any schema, including a generic one with no type
.
Conditional Schema
Using dependentRequired
, dependentSchemas
and if-then-else
are supported!
String Formats
JSON Schema provides a number of format keywords that require the validation to restrict the string to values matching the format. This module pulls in crystal-validator to perform most of the validations, and relies on regex for the rest.
Illogical Schema
It's possible to make a JSON schema that is logically impossible. For example, you could create a composite schema that checks that a value is both a number and a string. The generator tries to prevent this from happening. Since the schema is processed through macros, it seems better to present you a compile error that the schema is illogical than to allow you to use a schema that would be additional work to support in the code, but not actually validate anything.
Calling this out here as this is different behavior than most JSON Schema tools out there. Many libraries implement the rules and rely on the user to write a logical schema.
Unsupported
These features of JSON Schema are not yet supported, but will be supported in a future release (at least before 1.0.0
).
- References,
$id
,$anchor
, and any other relationship-driven schema definition. - Media Types
Dialects
The latest revision of this shard only supports the latest revision of JSON Schema (2020-12). There is not yet support for using a different dialect.
i18n/Translation
You may provide a translation to this module through JSONSchema.i18n
. Please see the API docs for documentation.
Acknowledgements
The source for this shard is inspired by the ECR
and JSON
implementations from the std lib. Thanks to the Crystal team for creating an amazing standard library!