jesse (JSON Schema Erlang) is an implementation of a JSON Schema validator for Erlang, though it can work just as well as a CLI tool.
jesse implements the following specifications:
Install from git or https://hex.pm/packages/jesse .
For convenience, available as a Docker image too: docker run --rm -it -v $PWD:$PWD -w $PWD ysoftwareab/jesse ...
.
And whalebrew package: brew install whalebrew && sudo whalebrew install ysoftwareab/jesse; jesse ...
Automatically generated docs are available https://dev.erldocs.com/github.com/for-get/jesse/ .
Please keep in mind that the public API is the jesse.erl
module alone.
You can either build the bin/jesse
executable yourself (just type make
),
or you can use a Docker image a call it with docker run ysoftwareab/jesse
.
You can fire up jesse
from the CLI, with
bin/jesse [path_to_json_schema] path_to_json_schema -- path_to_json_instance [path_to_json_instance]
You can also output the result in JSON format, with --json
, and beautify it e.g. with python
bin/jesse [path_to_json_schema] path_to_json_schema --json -- path_to_json_instance [path_to_json_instance] | python -m json.tool
You can pass multiple JSON schemas which should be loaded into jesse in-memory storage, but JSON instances will be validated against the last JSON schema passed.
There are two ways of using jesse:
NOTE: jesse doesn't have any parsing functionality. It currently works with four
formats: mochijson2, jiffy, jsx and Erlang 17+ maps, so JSON needs to be
parsed in advance, or you can specify a callback which jesse will use to
parse JSON.
In examples below and in jesse test suite jiffy parser is used.
(parse JSON in advance)
1> Schema = jiffy:decode(<<"{\"items\": {\"type\": \"integer\"}}">>).
{[{<<"items">>,{[{<<"type">>,<<"integer">>}]}}]}
2> jesse:add_schema("some_key", Schema).
ok
3> Json1 = jiffy:decode(<<"[1, 2, 3]">>).
[1,2,3]
4> jesse:validate("some_key", Json1).
{ok,[1,2,3]}
5> Json2 = jiffy:decode(<<"[1, \"x\"]">>).
[1,<<"x">>]
6> jesse:validate("some_key", Json2).
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
wrong_type,<<"x">>,
[1]}]}
The [1]
in the error is the path in the original value to <<"x">>
where the
validation failed. See Validation errors below for the full error format.
(using a callback)
1> jesse:add_schema("some_key",
1> <<"{\"uniqueItems\": true}">>,
1> [{parser_fun, fun jiffy:decode/1}]).
ok
2> jesse:validate("some_key",
2> <<"[1, 2]">>,
2> [{parser_fun, fun jiffy:decode/1}]).
{ok,[1, 2]}
3> jesse:validate("some_key",
3> <<"[{\"foo\": \"bar\"}, {\"foo\": \"bar\"}] ">>,
3> [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"uniqueItems">>,true}]},
{not_unique,{[{<<"foo">>,<<"bar">>}]}},
[{[{<<"foo">>,<<"bar">>}]},{[{<<"foo">>,<<"bar">>}]}],
[]}]}
(parse JSON in advance)
1> Schema = jiffy:decode(<<"{\"pattern\": \"^a*$\"}">>).
{[{<<"pattern">>,<<"^a*$">>}]}
2> Json1 = jiffy:decode(<<"\"aaa\"">>).
<<"aaa">>
3> jesse:validate_with_schema(Schema, Json1).
{ok,<<"aaa">>}
4> Json2 = jiffy:decode(<<"\"abc\"">>).
<<"abc">>
5> jesse:validate_with_schema(Schema, Json2).
{error,[{data_invalid,{[{<<"pattern">>,<<"^a*$">>}]},
no_match,
<<"abc">>,[]}]}
(using a callback)
1> Schema = <<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>.
<<"{\"patternProperties\": {\"f.*o\": {\"type\": \"integer\"}}}">>
2> jesse:validate_with_schema(Schema,
2> <<"{\"foo\": 1, \"foooooo\" : 2}">>,
2> [{parser_fun, fun jiffy:decode/1}]).
{ok,{[{<<"foo">>,1},{<<"foooooo">>,2}]}}
3> jesse:validate_with_schema(Schema,
3> <<"{\"foo\": \"bar\", \"fooooo\": 2}">>,
3> [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"integer">>}]},
wrong_type,<<"bar">>,
[<<"foo">>]}]}
1> Schema = <<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>.
<<"{\"properties\": {\"a\": {\"type\": \"integer\"}, \"b\": {\"type\": \"string\"}, \"c\": {\"type\": \"boolean\"}}}">>
2> jesse:validate_with_schema(Schema,
2> <<"{\"a\": 1, \"b\": \"b\", \"c\": true}">>,
2> [{parser_fun, fun jiffy:decode/1}]).
{ok,{[{<<"a">>,1},{<<"b">>,<<"b">>},{<<"c">>,true}]}}
now let's change the value of the field "b" to an integer
3> jesse:validate_with_schema(Schema,
3> <<"{\"a\": 1, \"b\": 2, \"c\": true}">>,
3> [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
wrong_type,2,
[<<"b">>]}]}
works as expected, but let's change the value of the field "c" as well
4> jesse:validate_with_schema(Schema,
4> <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
4> [{parser_fun, fun jiffy:decode/1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
wrong_type,2,
[<<"b">>]}]}
still works as expected, jesse stops validating as soon as finds an error.
Let's use the allowed_errors
option, and set it to 1
5> jesse:validate_with_schema(Schema,
5> <<"{\"a\": 1, \"b\": 2, \"c\": 3}">>,
5> [{parser_fun, fun jiffy:decode/1},
5> {allowed_errors, 1}]).
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
wrong_type,3,
[<<"c">>]},
{data_invalid,{[{<<"type">>,<<"string">>}]},
wrong_type,2,
[<<"b">>]}]}
now we got a list of two errors.
Let's now change the value of the field "a" to a boolean
6> jesse:validate_with_schema(Schema,
6> <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
6> [{parser_fun, fun jiffy:decode/1},
6> {allowed_errors, 1}]).
{error,[{data_invalid,{[{<<"type">>,<<"string">>}]},
wrong_type,2,
[<<"b">>]},
{data_invalid,{[{<<"type">>,<<"integer">>}]},
wrong_type,true,
[<<"a">>]}]}
we still got only two errors.
Let's try using 'infinity' as the argument for the allowed_errors
option
7> jesse:validate_with_schema(Schema,
7> <<"{\"a\": true, \"b\": 2, \"c\": 3}">>,
7> [{parser_fun, fun jiffy:decode/1},
7> {allowed_errors, infinity}]).
{error,[{data_invalid,{[{<<"type">>,<<"boolean">>}]},
wrong_type,3,
[<<"c">>]},
{data_invalid,{[{<<"type">>,<<"string">>}]},
wrong_type,2,
[<<"b">>]},
{data_invalid,{[{<<"type">>,<<"integer">>}]},
wrong_type,true,
[<<"a">>]}]}
Maps example
8> jesse:validate_with_schema(Schema,
8> <<"{\"a\": 1, \"b\": 2, \"c\": true}">>,
8> [{parser_fun, fun(Bin) -> jiffy:decode(Bin, [return_maps]) end}]).
{error,[{data_invalid,#{<<"type">> => <<"string">>},
wrong_type,2,
[<<"b">>]}]}
9> jesse:validate_with_schema(Schema,
9> <<"{\"a\": 1, \"b\": \"val\", \"c\": true}">>,
9> [{parser_fun, fun(Bin) -> jiffy:decode(Bin, [return_maps]) end}]).
{ok, #{<<"a">> => 1, <<"b">> => <<"val">>, <<"c">> => true}}
jesse currently supports JSON Schema draft3, draft4 and draft6. To decide which
validator to use jesse tries to read $schema
property from the given schema,
and checks if it's a supported one, otherwise it will return an error.
If $schema
property isn't provided in the given schema, jesse will use the
default validator (currently the validator for draft3).
To specify which validator to use by default (if there's no schema
property in
the given schema), one should use 'default_schema_ver' option when call
jesse:validate/3
or jesse:validate_with_schema/3
, the value should be
a binary consisting a schema path,
i.e. <<"http://json-schema.org/draft-03/schema#">>.
The validation functions jesse:validate/2
and jesse:validate_with_schema/2,3
return {ok, Value}
on success and {error, ListOfErrors}
on failure. An error
is either data_invalid
or schema_invalid
.
A data_invalid
error is a tuple on the form {data_invalid, Schema, ErrorType, Value, Path}
where
wrong_type
,
not_in_range
or no_match
{"foo": [42, 43, 44]}
, the path [<<"foo">>, 0]
refers to the value 42. An
empty list refers to the whole JSON document.A schema_invalid
error is a tuple on the form {schema_invalid, Schema, ErrorType}
where
missing_id_field
or a tuple such as
{wrong_type_dependency, Dependency}
.pattern and patternProperty attributes:
jesse uses standard erlang module re
for regexp matching, therefore there
could be some incompatible regular expressions in schemas you define.
From the erlang docs:
The matching algorithms of the library are based on the PCRE library, but not all of the PCRE library is interfaced and some parts of the library go beyond what PCRE offers.
Most common cases should work fine. Note that jesse provides an application
environment setting, re_options
(default: [unicode, ucp]
), for customizing
its use of the re
module. ucp
provides the widest compatibility for
matching unicode code points beyond ISO Latin-1 in character classes like
\w
, \s
, and \d
, but hurts performance. If maximum compatibility is not
required and performance is a significant concern, set re_options
to
[unicode]
instead. See notes on the ucp
option at
re:compile/2
for more
details.
internal references (id attribute) are NOT supported
http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.1
If you see something missing or incorrect, a pull request is most welcome!