/activerecord_json_validator

🔩 ActiveRecord::JSONValidator makes it easy to validate JSON attributes against a JSON schema.

Primary LanguageRubyBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause


ActiveRecord::JSONValidator makes it easy to validate
JSON attributes against a JSON schema.


Installation

Add this line to your application's Gemfile:

gem 'activerecord_json_validator', '~> 2.1.0'

Usage

JSON Schema

Schemas should be a JSON file

{
  "type": "object",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "properties": {
    "city": { "type": "string" },
    "country": { "type": "string" }
  },
  "required": ["country"]
}

Ruby

create_table "users" do |t|
  t.string "name"
  t.json "profile" # First-class JSON with PostgreSQL, yo.
end

class User < ActiveRecord::Base
  # Constants
  PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json')

  # Validations
  validates :name, presence: true
  validates :profile, presence: true, json: { schema: PROFILE_JSON_SCHEMA }
end

user = User.new(name: 'Samuel Garneau', profile: { city: 'Quebec City' })
user.valid? # => false

user = User.new(name: 'Samuel Garneau', profile: { city: 'Quebec City', country: 'Canada' })
user.valid? # => true

user = User.new(name: 'Samuel Garneau', profile: '{invalid JSON":}')
user.valid? # => false
user.profile_invalid_json # => '{invalid JSON":}'

Options

Option Description
:schema The JSON schema to validate the data against (see Schema section)
:value The actual value to use when validating (see Value section)
:message The ActiveRecord message added to the record errors (see Message section)
:options A Hash of json_schemer-supported options to pass to the validator
Schema

ActiveRecord::JSONValidator uses the json_schemer gem to validate the JSON data against a JSON schema.

Additionally, you can use a Symbol or a Proc. Both will be executed in the context of the validated record (Symbol will be sent as a method and the Proc will be instance_execed)

class User < ActiveRecord::Base
  # Constants
  PROFILE_REGULAR_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema')
  PROFILE_ADMIN_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile_admin.json_schema')

  # Validations
  validates :profile, presence: true, json: { schema: lambda { dynamic_profile_schema } } # `schema: :dynamic_profile_schema` would also work

  def dynamic_profile_schema
    admin? ? PROFILE_ADMIN_JSON_SCHEMA : PROFILE_REGULAR_JSON_SCHEMA
  end
end

The schema is passed to the JSONSchemer.schema function, so it can be anything supported by it:

class User < ActiveRecord::Base
  # Constants
  JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema')
  # JSON_SCHEMA = { 'type' => 'object', 'properties' => { 'foo' => { 'type' => 'integer', 'minimum' => 3 } } }
  # JSON_SCHEMA = '{"type":"object","properties":{"foo":{"type":"integer","minimum":3}}}'

  # Validations
  validates :profile, presence: true, json: { schema: JSON_SCHEMA }
end
Value

By default, the validator will use the “getter” method to the fetch attribute value and validate the schema against it.

# Will validate `self.foo`
validates :foo, json: { schema: SCHEMA }

But you can change this behavior if the getter method doesn’t return raw JSON data (a Hash):

# Will validate `self[:foo]`
validates :foo, json: { schema: SCHEMA, value: ->(record, _, _) { record[:foo] } }

You could also implement a “raw getter” if you want to avoid the value option:

# Will validate `self[:foo]`
validates :raw_foo, json: { schema: SCHEMA }

def raw_foo
  self[:foo]
end
Message

Like any other ActiveModel validation, you can specify either a Symbol or String value for the :message option. The default value is :invalid_json.

However, you can also specify a Proc that returns an array of errors. The Proc will be called with a single argument — an array of errors returned by the JSON schema validator. So, if you’d like to add each of these errors as a first-level error for the record, you can do this:

class User < ActiveRecord::Base
  # Validations
  validates :profile, presence: true, json: { message: ->(errors) { errors }, schema: 'foo.json_schema' }
end

user = User.new.tap(&:valid?)
user.errors.full_messages
# => [
#      'The property '#/email' of type Fixnum did not match the following type: string in schema 2d44293f-cd9d-5dca-8a6a-fb9db1de722b#',
#      'The property '#/full_name' of type Fixnum did not match the following type: string in schema 2d44293f-cd9d-5dca-8a6a-fb9db1de722b#',
#    ]

License

ActiveRecord::JSONValidator is © 2013-2022 Mirego and may be freely distributed under the New BSD license. See the LICENSE.md file.

The tree logo is based on this lovely icon by Sara Quintana, from The Noun Project. Used under a Creative Commons BY 3.0 license.

About Mirego

Mirego is a team of passionate people who believe that work is a place where you can innovate and have fun. We're a team of talented people who imagine and build beautiful Web and mobile applications. We come together to share ideas and change the world.

We also love open-source software and we try to give back to the community as much as we can.