Подтвердить что ты не робот

Как проверить, является ли строка 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

Как это сделать?

4b9b3361

Ответ 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