ActiveRecord::JSONValidator makes it easy to validate
JSON attributes against a JSON schema.
Add this line to your application's Gemfile:
gem 'activerecord_json_validator', '~> 3.1.0'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"]
}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":}'| 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 |
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
endThe 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 }
endBy 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]
endLike 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#',
# ]The tests require a database. We've provided a simple docker-compose.yml that will make
it trivial to run the tests against PostgreSQL. Simply run docker compose up -d
followed by rake spec. When you're done, run docker compose down to stop the database.
In order to use another database, simply define the DATABASE_URL environment variable
appropriately.
ActiveRecord::JSONValidator is © 2013-2025 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.
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.