Respect is a DSL to concisely describe the structure of common data such as hash and array using Ruby code. It comes with a validator, a sanitizer and dumpers to generate valid json-schema.org compliant specifications. Although it was designed to specify JSON schema, it can be used for any data represented as Hash and Array. It does not require any JSON parser since it works only on data.
Respect is named after the contraction of REST and SPEC. Indeed, it was first intended to be used to specify REST API in the context of a Web application.
There is a plugin called Respect for Rails which integrate this gem in Rails.
Already available:
- Compact Ruby DSL to specify your schema.
- Standard json-schema.org specification generator.
- Validator for JSON document or the like.
- Contextual validation error.
- Object sanitizer: turn plain string and integer values into real objects.
- Extensible API to add your custom validator and sanitizer.
- Extensible macro definition system to factor your schema definition code.
See the RELEASE_NOTES file for detailed feature listing.
Respect comes with a compact Ruby DSL to specify the structure of a hash and/or an array. Thus, it is ideal to specify JSON schema. I find it a way more concise than json-schema.org. And it is plain Ruby so you can rely on all great Ruby features to factor your specification code.
For instance, this ruby code specifies how one could structure a very simple user profile:
schema = Respect::HashSchema.define do |s|
s.string "name"
s.integer "age", greater_than: 18
s.string "email", format: :email
end
You can easily convert this specification to JSON Schema specification draft v3 so that all your customers can read and understand it:
require 'json'
puts JSON.pretty_generate(schema.to_h)
prints
{
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true
},
"age": {
"type": "integer",
"required": true,
"minimum": 18,
"exclusiveMinimum": true
},
"email": {
"type": "string",
"required": true,
"format": "email"
}
}
}
As you can see the Ruby specification is 4 times shorter than the JSON Schema specification...
You can also use this specification to validate JSON documents:
schema.validate?({ "name" => "My name", "age" => 20, "email" => "[email protected]" }) #=> true
schema.validate?({ "name" => "My name", "age" => 15, "email" => "[email protected]" }) #=> false
When it fails to validate a document, you get descriptive error messages with contextual information:
schema.last_error #=> "15 is not greater than 18"
schema.last_error.message #=> "15 is not greater than 18"
schema.last_error.context[0] #=> "15 is not greater than 18"
schema.last_error.context[1] #=> "in hash property `age'"
Respect does not parse JSON document by default but it is easy to do so using one of the JSON parser available in Ruby:
schema.validate?(JSON.parse('{ "name": "My name", "age": 20, "email": "[email protected]" }')) #=> true
Once a JSON document has been validated, we often want to turn its basic strings and integers into real objects like URI
for instance. Respect does that automatically for you for standard objects:
schema = Respect::HashSchema.define do |s|
s.uri "homepage"
end
object = { "homepage" => "http://example.com" }
schema.validate!(object) #=> true
object["homepage"].class #=> URI::HTTP
You can easily extend the sanitizer with your own object type. Let's assume you have a class defined like this:
class Place
def initialize(latitude, longitude)
@latitude, @longitude = latitude, longitude
end
attr_reader :latitude, :longitude
def ==(other)
@latitude == other.latitude && @longitude == other.longitude
end
end
Then you can extend the Schema
class hierarchy with the new schema for your custom type.
The CompositeSchema
class assists you in this task so you just have to override
two methods.
module Respect
class PlaceSchema < CompositeSchema
# This method returns the schema specification for your custom type.
def schema_definition
Respect::HashSchema.define do |s|
s.float "latitude"
s.float "longitude"
end
end
# The 'sanitize' method is called with the JSON document if the validation succeed.
# The returned value will be inserted into the JSON document.
def sanitize(object)
Place.new(object[:latitude], object[:longitude])
end
end
end
Finally, you define the structure of your JSON document as usual. Note that you have
access to your custom schema via the place
method.
schema = Respect::HashSchema.define do |s|
s.place "home"
end
object = {
"home" => {
"latitude" => "48.846559",
"longitude" => "2.344519",
}
}
schema.validate!(object) #=> true
object["home"].class #=> Place
Sometimes you just want to extend the DSL with a new statement providing higher level features than
the primitives integer
, string
or float
, etc... For instance if you specify identifier
in your schema like this:
Respect::HashSchema.define do |s|
s.integer "article_id", greater_than: 0
s.string "title"
s.hash "author" do |s|
s.integer "author_id", greater_than: 0
s.string "name"
end
end
In such case, you don't need a custom sanitizer since an identifer is an integer after all. You just want to factor the definition of an identifier property. You can easily do it like this:
module MyMacros
def id(name = "id", options = {})
unless name.nil? || name == "id" || name =~ /_id$/
name += "_id"
end
integer(name, { greater_than: 0 }.merge(options))
end
end
Respect.extend_dsl_with(MyMacros)
Now you can rewrite your schema definition this way:
Respect::HashSchema.define do |s|
s.id "article"
s.string "title"
s.hash "author" do |s|
s.id "author"
s.string "name"
end
end
The easiest way to install Respect is to add it to your Gemfile
:
gem "respect"
Then, after running the bundle install
command, you can start to validate JSON document in your program like this:
require 'respect'
schema = Respect::HashSchema.define do |s|
s.string "name"
s.integer "age", greater_than: 18
end
schema.validate?({ "name" => "John", "age" => 30 })
Respect currently implements most of the features included in the JSON Schema specification draft v3.
See the STATUS_MATRIX
file included in this package for detailed information.
Although, the semantics of the schema definition DSL available in this library may change slightly from the
JSON schema standard, we have tried to keep it as close as possible. For instance the strict
option of
hash schema is not presented in the standard. However, when a schema is dumped to its JSON Schema version
the syntax and semantic have been followed. You should note that there is no "loader" available yet in this
library. In other word, you cannot instantiate a Schema
class from a JSON Schema string representation.
The easiest way to get help about how to use this library is to post your question on the Respect discussion group. I will be glade to answer. I may have already answered the same question so before you post your question take a bit of time to search the group.
You can also read these documents for further documentation:
Respect has been tested with:
- Ruby 1.9.3-p392 (should be compatible with all 1.9.x family)
- ActiveSupport 3.2.13
Note that it does not depend on any JSON parsing library. It works only on primitive Ruby data type. So,
any JSON parser returning normal basic types like Hash
, Array
, String
, Numeric
, TrueClass
,
FalseClass
, NilClass
, should work.
I would love to hear what you think about this library. Feel free to post any comments/remarks on the Respect discussion group.
I spent quite a lot of time writing this gem but there is still a lot of work to do. Whether it
is a bug-fix, a new feature, some code re-factoring, or documentation clarification, I will be
glade to merge your pull-request on GitHub. You just have to create a branch from master
and
send me a pull request.
Respect is released under the term of the MIT License. Copyright (c) 2013 Nicolas Despres.