Как проверить, является ли строка json в модели Rails
Я создаю простое приложение и хочу иметь возможность хранить строки json в db. У меня есть интерфейс таблицы с столбцом json, и я хочу, чтобы моя модель рельсов проверяла значение строки. Так что-то вроде:
class Interface < ActiveRecord::Base
attr_accessible :name, :json
validates :name, :presence => true,
:length => { :minimum => 3,
:maximum => 40 },
:uniqueness => true
validates :json, :presence => true,
:type => json #SOMETHING LIKE THIS
:contains => json #OR THIS
end
Как это сделать?
Ответы
Ответ 1
Я предполагаю, что вы могли бы проанализировать поле в вопросе и посмотреть, вызывает ли оно ошибку. Здесь приведен упрощенный пример (вы можете отказаться от двойного взлома для чего-то более ясного):
require 'json'
class String
def is_json?
begin
!!JSON.parse(self)
rescue
false
end
end
end
Затем вы можете использовать это расширение строки в пользовательском валидаторе.
validate :json_format
protected
def json_format
errors[:base] << "not in json format" unless json.is_json?
end
Ответ 2
Лучший способ - добавить метод к модулю JSON!
Поместите это в свой config/application.rb:
module JSON
def self.is_json?(foo)
begin
return false unless foo.is_a?(String)
JSON.parse(foo).all?
rescue JSON::ParserError
false
end
end
end
Теперь вы сможете использовать его в любом месте ( "контроллер, модель, вид,..." ), например:
puts 'it is json' if JSON.is_json?(something)
Ответ 3
В настоящее время (Rails 3/Rails 4) я бы предпочел настраиваемый валидатор. Также см. https://gist.github.com/joost/7ee5fbcc40e377369351.
# Put this code in lib/validators/json_validator.rb
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(:message => :invalid)
super(options)
end
def validate_each(record, attribute, value)
value = value.strip if value.is_a?(String)
ActiveSupport::JSON.decode(value)
rescue MultiJson::LoadError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
Ответ 4
У меня возникла другая проблема с использованием Rails 4.2.4 и адаптера PostgreSQL (pg) и настраиваемого валидатора для моего json-поля.
В следующем примере:
class SomeController < BaseController
def update
@record.json_field = params[:json_field]
end
end
если вы передадите недействительный JSON на
params[:json_field]
он тихо игнорируется, а "nil" хранится в
@record.json_field
Если вы используете настраиваемый валидатор, например
class JsonValidator < ActiveModel::Validator
def validate(record)
begin
JSON.parse(record.json_field)
rescue
errors.add(:json_field, 'invalid json')
end
end
end
вы не увидите недопустимую строку в
record.json_field
только значение "nil", потому что rails выполняет литье типов перед передачей вашего значения в validator. Чтобы преодолеть это, просто используйте
record.json_field_before_type_cast
в вашем валидаторе.
Ответ 5
Используя парсер JSON, возможна чистая проверка формата JSON. ActiveSupport::JSON.decode(value)
проверяет значение "123"
и 123
на true. Это неверно!
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(message: :invalid)
super(options)
end
def validate_each(record, attribute, value)
if value.is_a?(Hash) || value.is_a?(Array)
value = value.to_json
elsif value.is_a?(String)
value = value.strip
end
JSON.parse(value)
rescue JSON::ParserError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
Ответ 6
Если вам не нравятся валидаторы корпоративного стиля или неуклюжие исправления класса String, вот простое решение:
class Model < ApplicationRecord
validate :json_field_format
def parsed_json_field
JSON.parse(json_field)
end
private
def json_field_format
return if json_field.blank?
begin
parsed_json_field
rescue JSON::ParserError => e
errors[:json_field] << "is not valid JSON"
end
end
end
Ответ 7
Самый простой и элегантный способ, ИМО. Ответы с наибольшим количеством голосов будут возвращать true при передаче строки, содержащей целые числа или числа с плавающей точкой, или в этом случае выдают ошибку.
def valid_json?(string)
hash = Oj.load(string)
hash.is_a?(Hash) || hash.is_a?(Array)
rescue Oj::ParseError
false
end