【问题标题】:How to validate if a string is json in a Rails model如何在 Rails 模型中验证字符串是否为 json
【发布时间】:2011-09-29 11:18:39
【问题描述】:

我正在构建一个简单的应用程序,并希望能够将 json 字符串存储在数据库中。我有一个带有 json 列的表接口,我希望我的 rails 模型来验证字符串的值。所以像:

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

我该怎么做?

【问题讨论】:

    标签: ruby-on-rails json validation


    【解决方案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
    

    【讨论】:

    • 一切都变绿了,谢谢!响应时间也很长:) 我一直在尝试做这样的事情,但我的 Rails 技能有点尘土飞扬。
    • 嗯,这很奇怪。我有 rspec 测试,其中一个需要一个有效的 json 字符串作为 json 的值,并且这些都是绿色的。但是我的黄瓜测试失败了,并且在 rails 服务器中测试视图也失败了,说明 is_json?是一个未定义的方法。我把你建议的验证类放在了我的模型下面,有错吗?
    • 我想有不同的意见,但似乎大多数人将他们的核心类扩展放在config/initializers/ 中(自然地)在 Rails 加载后它们会自动加载。另一个选项是lib/ 目录,但是你必须告诉 Rails 仍然加载你的文件。
    • 该解决方案效果很好,我投了赞成票,但请允许我简短地说一句:使用自己的方法扩展基本对象通常被认为不是最佳实践。我更愿意实现 is_json?辅助类或模块中的方法,并提供 'suspected_json' 字符串作为该方法的参数。虽然能够在这里使用 self 真的很优雅:)
    • @awenkhh 2014 年的我必须同意你而不是 2011 年的我。也许是改进的案例;)?
    【解决方案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
      

      【讨论】:

      • 我实际上比接受的答案更喜欢这个想法。使用 MultiJson.load(value) 而不是 ActiveSupport::JSON.decode(value) 怎么样?然后它也会跑到救援块...
      【解决方案4】:

      使用 JSON 解析器,可以进行纯 JSON 格式验证。 ActiveSupport::JSON.decode(value) 验证值 "123"123 为真。这是不正确的!

      # 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
      

      【讨论】:

        【解决方案5】:

        我在使用 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 在将你的值传递给验证器之前会进行类型转换。为了克服这个问题,只需使用

        record.json_field_before_type_cast
        

        在您的验证器中。

        【讨论】:

          【解决方案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
          

          【讨论】:

          • 您是否会替换 json_field - 如:validate :options_formatparsed_options
          【解决方案7】:

          最简单优雅的方式,imo。在传递包含整数或浮点数的字符串时,获得最高票数的答案要么返回 true,要么在这种情况下抛出错误。

          def valid_json?(string)
              hash = Oj.load(string)
              hash.is_a?(Hash) || hash.is_a?(Array)
          rescue Oj::ParseError
              false
          end
          

          【讨论】:

            猜你喜欢
            • 2017-06-10
            • 2012-07-02
            • 2012-08-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-25
            • 2017-11-27
            相关资源
            最近更新 更多