web-dev-qa-db-fra.com

Comment valider si une chaîne est json dans un modèle Rails

Je construis une application simple et je veux pouvoir stocker des chaînes JSON dans une base de données. J'ai une table Interface avec une colonne json et je veux que mon modèle Rails valide la valeur de la chaîne. Donc, quelque chose comme:

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

Comment je fais ça?

28
Jasper Kennis

Je suppose que vous pouvez analyser le champ en question et voir s’il génère une erreur. Voici un exemple simplifié (vous pouvez laisser tomber le double coup pour quelque chose un peu plus clair):

require 'json'

class String
  def is_json?
    begin
      !!JSON.parse(self)
    rescue
      false
    end
  end
end

Ensuite, vous pouvez utiliser cette extension de chaîne dans un validateur personnalisé.

validate :json_format

protected

  def json_format
    errors[:base] << "not in json format" unless json.is_json?
  end
36
polarblau

Le meilleur moyen consiste à ajouter une méthode au module JSON!

_ {Mettez ceci dans votre 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

Vous pourrez maintenant l'utiliser n'importe où ('contrôleur, modèle, vue, ...'), comme ceci:

puts 'it is json' if JSON.is_json?(something)
16
Alain Beauvois

Actuellement (Rails 3/Rails 4), je préférerais un validateur personnalisé . Voir aussi 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
15
joost

J'ai rencontré un autre problème en utilisant Rails 4.2.4 et l'adaptateur PostgreSQL (pg) et un validateur personnalisé pour mon champ json. 

Dans l'exemple suivant:

class SomeController < BaseController
  def update
    @record.json_field = params[:json_field]
  end
end

si vous transmettez un JSON invalide à 

params[:json_field]

il est discrètement ignoré et "nil" est stocké dans

@record.json_field

Si vous utilisez un validateur personnalisé comme 

class JsonValidator < ActiveModel::Validator
  def validate(record)
    begin
      JSON.parse(record.json_field)
    rescue
      errors.add(:json_field, 'invalid json')
    end
  end
end

vous ne verriez pas de chaîne invalide dans 

record.json_field

uniquement la valeur "nil", car Rails saisit la conversion avant de transmettre votre valeur au validateur. Afin de surmonter cela, il suffit d'utiliser

record.json_field_before_type_cast

dans votre validateur.

3
Dmitry Vershinin

Grâce à l’analyseur JSON, la validation au format JSON pur est possible. ActiveSupport::JSON.decode(value) valide la valeur "123" et 123 sur true. Ce n'est pas correct!

# 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
0
phlegx

Si vous n'aimez pas les validateurs de style entreprise ou la mise à jour des clés de la classe String, voici une solution simple:

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] << "not is json format" 
    end
  end
end
0
thisismydesign