DEV Community

Cover image for Tired of json-schema, try dry-validation
Alireza Bashiri
Alireza Bashiri

Posted on

Tired of json-schema, try dry-validation

Why you should use dry-validation over json-schema?

You working with JSON responses coming from APIs and you want to validate their schemas? What’s your first choice? Probably you go with the great way that Thoughtbot guys explained in this article. I’ve launched and tested various projects based on that technique but I always felt I’m doing something wrong because the process was so painful. Let me explain; If you want to validate a JSON response first you should create a JSON schema based on it then you’d write your test and get some errors because of a comma/double quotation being missed.

errors everywhere

An example of the old way of testing JSON responses. First, define the JSON schema:


{
  "type": "object",
  "required": ["user"],
  "properties": {
    "user" : {
      "type" : "object",
      "required" : [
        "id",
        "token",
        "token_expired_at",
        "email",
        "firstname",
        "lastname",
        "gender",
        "phone_number",
        "birthday"
      ],
      "properties" : {
        "id" : { "type" : "integer" },
        "token" : { "type" : "string" },
        "token_expired_at" : { "type" : "string" },
        "email" : { "type" : "string" },
        "lastname" : { "type" : "string" },
        "firstname" : { "type" : "string" },
        "gender" : { "type" : "string" },
        "phone_number" : { "type" : "string" },
        "brithday" : { "type" : "string", "format": "date-time" },
        "updated_at" : { "type" : "string", "format": "date-time" },
        "created_at" : { "type" : "string", "format": "date-time" },
      }
    }
  }
}

Then write your test

describe "Sign in" do
  context "anonymous user" do
    it "returns data in the specified format" do
      sign_in_as_anonymous

      expect(response).to match_response_schema("users/anonymous_detail")
    end
  end
end

Not speaking of bad errors, you have to switch to JSON schema syntax and Ruby over and over again (context switching) and there you are you’ve lost your time and focus. Now I’m going to show you a better way to validate your mighty JSON with ease.

Creating the custom matcher

# Gemfile
group :test do
  gem "dry-validation"
end
# spec/support/matchers/match_schema.rb
RSpec::Matchers.define :match_schema do |schema|
  match do |response|
    @result = schema.call(JSON.parse(response.body, symbolize_names: true))
    @result.success?
  end

  def failure_message
    @result.errors
  end
end

Note: Here I’ve chosen /spec/support/matchers/* for my custom matchers with a name convention of match_*. Also in schema.call schema is a dry-validation class and call initializes the schema class with the given parameters then I return the result of validation which is a true/false value. I’ve even defined failure_message to be able to see the errors that have been generated from validating the schema inside Rspec result.

Defining the anonymous user schema

Add this to your spec/rails_helper.rb

require "support/views/schemas"

Now defining anonymous user schema:

AnonymousUserSchema = Dry::Validation.Schema do
  required(:user).schema do
    required(:email)
    required(:token).filled
    required(:token_expired_at).filled
    required(:firstname)
    required(:lastname)
    required(:gender)
    required(:phone_number)
    required(:birthday)
  end
end

Now you can change your spec to this:

describe "Sign in" do
  context "anonymous user" do
    it "returns data in the specified format" do
      sign_in_as_anonymous

      expect(response).to match_schema(AnonymousUserSchema)
    end
  end
end

There you go! What if when I added a new field? You just add the field with the needed condition to end of the block.

AnonymousUserSchema = Dry::Validation.Schema do
  required(:user).schema do
    required(:email)
    required(:token).filled
    required(:token_expired_at).filled
    required(:firstname)
    required(:lastname)
    required(:gender)
    required(:phone_number)
    required(:birthday)
    required(:age).maybe(int?)
  end
end

With dry-validation you can do pretty much everything. For further information please check out the link down below.

http://dry-rb.org/gems/dry-validation/

I hope you have enjoyed :D

Latest comments (4)

Collapse
 
benbonnet profile image
Ben

is there any benefits compared to using a tool that cover the whole spec ? ex github.com/ruby-json-schema/json-s...

Collapse
 
alirezabashiri profile image
Alireza Bashiri

Not really if you're comfortable maintaining your JSON schemas through JSON files instead of Ruby DSL.

Collapse
 
kelseydh profile image
Kelsey Hannan

I love this! What file do you put the schema in?

I'm also interested in whether this could also be used within the API consumer itself, to render error messages or to detect an outage from malformed JSON.

Collapse
 
alirezabashiri profile image
Alireza Bashiri

We used this more on testing side but now days I'd recommend keep it simple and using just a minittest helper or a rspec helper instead of brining a whole library unless necessary and if I'm not wrong we used to write them in spce/schemas.