jsonschema

Version, currently main branch1 version
  • main branchlatestJul 19, 2022

github.com/aarongodin/jsonschema

Comprehensive json-schema validation and generator

7 stars
0 dependents
License: MIT

Installation

# Add this to your shard.yml
dependencies:
  jsonschema:
    github: aarongodin/jsonschema
    branch: main

main is a branch, not a release, so this tracks it rather than pinning a version.

Then run:

shards install

shard.yml

Crystal
>= 1.3.1
License
MIT
Author
Aaron Godin <godinaaweb@gmail.com>

Dependencies

Runtime Dependencies

  • validator*github: nicolab/crystal-validator

README

[![GitHub release](https://img.shields.io/github/release/aarongodin/jsonschema.svg)](https://github.com/aarongodin/jsonschema/releases)
[![CI](https://github.com/aarongodin/jsonschema/actions/workflows/crystal.yml/badge.svg)](https://github.com/aarongodin/jsonschema/actions/workflows/crystal.yml)
![GitHub](https://img.shields.io/github/license/aarongodin/jsonschema?style=plastic)

# jsonschema

Comprehensive [JSON Schema](https://json-schema.org/) validation and generator for Crystal. Read below for a high-level description of the API, or check out the [API Docs](https://aarongodin.github.io/jsonschema/) for full details.

## Installation

1. Add the dependency to your `shard.yml`:

   ```yaml
   dependencies:
     jsonschema:
       github: aarongodin/jsonschema
   ```

2. 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 to `JSONSchema.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.

```crystal
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`.

```crystal
class RequestBody
  JSONSchema.create_validator_method "my_schema.json"
end
```

This is syntactically equivalent to:

```crystal
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:

```crystal
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:
> ```crystal
> JSONSchema.create_validator "my_schema" # => Loads "my_schema.json"
> ```

### Validating JSON

Use the `#validate` method on any generated validator to receive a `JSONSchema::ValidationResult`:

```crystal
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`:

```crystal
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](https://stedolan.github.io/jq/)) uses for pathing.

```crystal
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?`.

```crystal
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](https://aarongodin.github.io/jsonschema/JSONSchema/Fluent.html) class for full usage specification. Here's an example that shows a complex schema represented using the fluent API:

```crystal
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:

```crystal
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` and `integer`
* `array`
* `object`
* `null`
* `boolean`

The generic keywords `const` and `enum` are also supported.

* `enum` can be provided either as a generic keyword (schema without a `type` 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](https://github.com/Nicolab/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](https://json-schema.org/understanding-json-schema/structuring.html#ref), `$id`, `$anchor`, and any other relationship-driven schema definition.
* [Media Types](https://json-schema.org/understanding-json-schema/reference/non_json_data.html)

### 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](https://aarongodin.github.io/jsonschema/JSONSchema/I18N.html) 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!