kidbrax kidbrax - 1 month ago 20
JSON Question

Is there a way to enforce a schema for a JSON field within a Postgresql record in Rails?

I am dabbling with json fields in Postgres. So I am using activerecord for most validation. But, I'd like to add a json field and be able to validate that. Is there a clean way to do this? I guess I could always create my own validation function but it seems like there would be a better way that may take advantage of json schemas? Has anyone had success with this?

max max
Answer

You can use the json-schema gem:

schema = {
  "type" => "object",
  "required" => ["a"],
  "properties" => {
    "a" => {"type" => "integer"}
  }
}
JSON::Validator.validate(schema, { "a" => 5 }) 
# => true
JSON::Validator.validate(schema, { "a" => "five" })
# => false 

Note that this a validator in the broader sense of the term - not a validator that can be directly used with ActiveRecord.

Writing a custom ActiveModel validator is pretty easy though:

require "json-schema"
class SchemaValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    # Looks for a JSON schema as a class constant
    c = "#{attribute.upcase}_SCHEMA"
    begin 
      schema = record.class.const_get(c)
    rescue NameError => e
      # re-raise exception with a more descriptive message
      raise( 
        $!, 
        "Expected #{record.class.name}::#{c} to declare a JSON Schema for #{attribute}", 
        $!.backtrace
      )
    end
    unless JSON::Validator.validate(schema, value)
      record.errors.add(attribute, 'does not comply to JSON Schema')
    end
  end
end

We can then use it like so:

class Thing < ActiveRecord::Base
  FOO_SCHEMA = {
    "type" => "object",
    "required" => ["a"],
    "properties" => {
      "a" => {"type" => "integer"}
    }
  }

  validates :foo, schema: true
end