How to validate if a string is json in a Rails model

前端 未结 7 663
悲&欢浪女
悲&欢浪女 2021-01-31 16:03

I\'m building a simple app and want to be able to store json strings in a db. I have a table Interface with a column json, and I want my rails model to validate the value of the

相关标签:
7条回答
  • 2021-01-31 16:39

    I suppose you could parse the field in question and see if it throws an error. Here's a simplified example (you might want to drop the double bang for something a bit clearer):

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

    Then you could use this string extension in a custom validator.

    validate :json_format
    
    protected
    
      def json_format
        errors[:base] << "not in json format" unless json.is_json?
      end
    
    0 讨论(0)
  • 2021-01-31 16:44

    If you don't fancy enterprise-style validators or monkey-patching the String class here's a simple solution:

    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
    
    0 讨论(0)
  • 2021-01-31 16:45

    Currently (Rails 3/Rails 4) I would prefer a custom validator. Also see 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
    
    0 讨论(0)
  • 2021-01-31 16:46

    I faced another problem using Rails 4.2.4 and PostgreSQL adapter (pg) and custom validator for my json field.

    In the following example:

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

    if you pass invalid JSON to

    params[:json_field]
    

    it is quietly ignored and "nil" is stored in

    @record.json_field
    

    If you use custom validator like

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

    you wouldn't see invalid string in

    record.json_field
    

    only "nil" value, because rails does type casting before passing your value to validator. In order to overcome this, just use

    record.json_field_before_type_cast
    

    in your validator.

    0 讨论(0)
  • 2021-01-31 16:57

    The most simple and elegant way, imo. The top upvoted answers will either return true when passing a string containing integers or floats, or throw an error in this case.

    def valid_json?(string)
        hash = Oj.load(string)
        hash.is_a?(Hash) || hash.is_a?(Array)
    rescue Oj::ParseError
        false
    end
    
    0 讨论(0)
  • 2021-01-31 16:58

    Using JSON parser, pure JSON format validation is possible. ActiveSupport::JSON.decode(value) validates value "123" and 123 to true. That is not 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 讨论(0)
提交回复
热议问题