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::Validatorobject. - Create
JSONSchema::Validatorinstances during runtime by providing JSON input toJSONSchema.from_json() - Build
JSONSchema::Validatorobjects 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
.jsonas 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!
stringnumberandintegerarrayobjectnullboolean
The generic keywords const and enum are also supported.
enumcan be provided either as a generic keyword (schema without atypevalue), or on a typed schema.constmay 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!