【问题标题】:Deserialize JSON primitives with the built-in Ruby JSON library使用内置的 Ruby JSON 库反序列化 JSON 基元
【发布时间】:2011-10-22 21:30:45
【问题描述】:

为什么 Ruby 的内置 JSON 不能反序列化简单的 JSON 原语,我该如何解决?

irb(main):001:0> require 'json'
#=> true

irb(main):002:0> objects = [ {}, [], 42, "", true, nil ]
#=> [{}, [], 42, "", true]

irb(main):012:0> objects.each do |o|
irb(main):013:1*   json = o.to_json
irb(main):014:1>   begin
irb(main):015:2*     p JSON.parse(json)
irb(main):016:2>   rescue Exception => e
irb(main):017:2>     puts "Error parsing #{json.inspect}: #{e}"
irb(main):018:2>   end
irb(main):019:1> end
{}
[]
Error parsing "42": 706: unexpected token at '42'
Error parsing "\"\"": 706: unexpected token at '""'
Error parsing "true": 706: unexpected token at 'true'
Error parsing "null": 706: unexpected token at 'null'
#=> [{}, [], 42, "", true, nil]

irb(main):020:0> RUBY_DESCRIPTION
#=> "ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.7.0]"
irb(main):022:0> JSON::VERSION
#=> "1.4.2"

【问题讨论】:

    标签: ruby json


    【解决方案1】:

    RFC 4627: The application/json Media Type for JavaScript Object Notation (JSON) 是这样说的:

    2.  JSON Grammar
    
       A JSON text is a sequence of tokens.  The set of tokens includes six
       structural characters, strings, numbers, and three literal names.
    
       A JSON text is a serialized object or array.
    
          JSON-text = object / array
    
    [...]
    
    2.1.  Values
    
       A JSON value MUST be an object, array, number, or string, or one of
       the following three literal names:
    
          false null true
    

    如果您在六个示例对象上调用 to_json,我们会得到:

    >> objects = [ {}, [], 42, "", true, nil ]
    >> objects.map { |o| puts o.to_json }
    {}
    []
    42
    ""
    true
    null
    

    所以第一个和第二个是有效的 JSON 文本,而最后四个不是有效的 JSON 文本,即使它们是有效的 JSON 值

    JSON.parse 想要它所谓的 JSON 文档:

    将 JSON 文档 source 解析为 Ruby 数据结构并返回。

    也许 JSON 文档 是该库对 RFC 4627 所称的 JSON 文本 的术语。如果是这样,那么引发异常是对无效输入的合理响应。

    如果你强行包装和打开所有东西:

    objects.each do |o|
        json = o.to_json 
        begin
            json_text = '[' + json + ']'
            p JSON.parse(json_text)[0]
        rescue Exception => e 
            puts "Error parsing #{json.inspect}: #{e}"    
        end    
    end
    

    正如您在评论中指出的那样,如果调用者想要使用 :symbolize_names 选项,使用数组作为包装器比使用对象更好。像这样包装意味着您将始终为 JSON.parse 提供 JSON 文本,一切都应该没问题。

    【讨论】:

    • 我建议使用数组包装器而不是对象包装器,以防用户想要将symbolize_names:true 传递给JSON.parse。使用数组,解包结果的方法不受影响。
    • 顺便说一句,很好的答案,引用了 RFC。虽然您提出的论点听起来很合理,但对于 JSON.parse(o.to_json) != o 而言,对于简单的可序列化值来说,这并不合理。
    • @Phrogz:很好,我已经相应地修补了我的答案。我同意无法解析非对象/数组值有点迂腐,尤其是在 DWIM Ruby 世界中,尤其是因为没有记录此类行为。
    • 注意this question 及其答案,这意味着即使 RFC 4627 将“JSON 文本”限制为对象或数组,官方 JSON 标准 ECMA 404 没有此限制。我现在确信这是 Ruby JSON 库中的一个错误(合理地,来自之前正在进行的规范),并且会努力修复它。
    • @Phrogz:我认为我们至少需要另外两个 JSON 标准可供选择。然后可能是一些稍微不兼容的实现,只是为了让一切变得有趣。
    【解决方案2】:

    这是一个相当古老的问题,但我认为值得为那些刚刚遇到问题并仍在寻找解决方案的人提供一个正确的答案来防止脱发:)

    为了能够使用 JSON gem 低于版本 2 解析“JSON 原语”,您可以像这样传递 quirks_mode: true 选项;

    JSON::VERSION # => 1.8.6
    
    json_text = "This is a json primitive".to_json
    JSON.parse(json_text, quirks_mode: true)
    

    如果 JSON gem 版本大于或等于 2,则不再需要 quirks_mode。

    JSON::VERSION # => 2.0.0
    
    json_text = "This is a json primitive".to_json
    JSON.parse(json_text)
    

    在解析 JSON 之前,您可以使用 bundle show jsongem list | grep json 检查您在项目中使用的 JSON gem 的版本,然后使用对应的版本。

    快乐的 JSON 解析!

    【讨论】:

      【解决方案3】:

      似乎内置的 JSON 解析器故意在除对象和数组之外的任何东西上失败。我目前的解决方法如下:

      # Work around a flaw in Ruby's built-in JSON parser
      # not accepting anything but an object or array at the root level.
      module JSON
        def self.parse_any(str,opts={})
          parse("[#{str}]",opts).first
        end
      end
      

      【讨论】:

      • 一个公平的组合,但我认为包装和展开比格式猜测更安全。
      • @muistooshort 一个很好的观点;总是包装和展开可能几乎不会影响性能。有什么理由你更喜欢使用对象而不是数组?
      • 可能值得进行一些基准测试,但我怀疑您是否能够在不使用少量 JSON 和数万次迭代的情况下检测到差异。
      【解决方案4】:

      使用JSON.load 而不是JSON.parse 来处理原语:

      例如

      JSON.load('true') # => true
      JSON.load('false') # => false
      JSON.load('5150') # => 5150
      JSON.load('null') # => nil
      

      【讨论】:

        【解决方案5】:

        我认为你是对的......无论它是否是一个错误,实施过程中都有一些奇怪的逻辑。如果它可以解析数组和散列,它应该能够解析其他所有内容。

        因为 JSON.parse 似乎适用于对象和数组,所以如果可以的话,我会尝试通过其中一种方式传递您的数据,如果不能,请坚持使用现有的解决方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-19
          • 2019-07-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多