【问题标题】:YAML Encoding of Malformed String, Model Serialization Issues畸形字符串的 YAML 编码,模型序列化问题
【发布时间】:2010-12-08 19:58:51
【问题描述】:

我已经隔离了 Ruby on Rails 的一个问题,其中带有序列化列的模型无法正确加载已保存到其中的数据。

输入的是哈希,输出的是由于格式问题而无法解析的 YAML 字符串。我希望序列化程序可以正确存储和检索您提供的任何内容,因此似乎出了点问题。

有问题的麻烦字符串格式如下:

message_text = <<END

  X
X
END

yaml = message_text.to_yaml

puts yaml
# =>
# --- |
#
#   X
# X

puts YAML.load(yaml)
# => ArgumentError: syntax error on line 3, col 0: ‘X’

换行符、缩进的第二行和不缩进的第三行的组合会导致解析器失败。省略空行或缩进似乎可以解决问题,但这似乎是序列化过程中的一个错误。由于它需要一组相当独特的情况,我敢打赌这是一些没有得到妥善处理的奇怪边缘情况。

Ruby 附带并由 Rails 使用的 YAML 模块看起来将大部分处理委派给 Syck,但确实为 Syck 提供了一些关于如何对其发送的数据进行编码的提示。

在 yaml/rubytypes.rb 中有 String#to_yaml 定义:

class String
  def to_yaml( opts = {} )
    YAML::quick_emit( is_complex_yaml? ? self : nil, opts ) do |out|
      if is_binary_data?
        out.scalar( "tag:yaml.org,2002:binary", [self].pack("m"), :literal )
      elsif to_yaml_properties.empty?
        out.scalar( taguri, self, self =~ /^:/ ? :quote2 : to_yaml_style )
      else
        out.map( taguri, to_yaml_style ) do |map|
          map.add( 'str', "#{self}" )
          to_yaml_properties.each do |m|
            map.add( m, instance_variable_get( m ) )
          end
        end
      end
    end
  end
end

那里似乎有一个检查以':'开头的字符串,并且在反序列化时可能会被混淆为符号,并且 :quote2 选项应该是在编码过程中引用它的指示。调整这个正则表达式来捕捉上述条件似乎对输出没有任何影响,所以我希望更熟悉 YAML 实现的人可以提出建议。

【问题讨论】:

    标签: ruby-on-rails ruby yaml syck


    【解决方案1】:

    是的,这看起来像是 C syck 库中的一个错误。我使用 PHP syck 绑定(v 0.9.3)检查了它:http://pecl.php.net/package/syck 并且存在相同的错误,表明它是库中的错误,而不是 ruby​​ yaml 库或 ruby​​-syck 绑定:

    // phptestsyck.php
    <?php
    $message_text = "
    
      X
    X
    ";
    
    syck_load(syck_dump($message_text));
    ?>
    

    在 cli 上运行它会产生相同的 SyckException:

    $ php phptestsyck.php 
    PHP Fatal error:  Uncaught exception 'SyckException' with message 'syntax error on line 5, col 0: 'X'' in /.../phptestsyck.php:8
    Stack trace:
    #0 /.../phptestsyck.php(8): syck_load('--- %YAML:1.0 >...')
    #1 {main}
      thrown in /.../phptestsyck.php on line 8
    

    所以,我想你可以尝试修复 Syck 本身。不过,该库似乎自 2005 年 5 月 (http://rubyforge.org/projects/syck/) 的 v0.55 以来一直没有更新。

    另外,还有一个名为 RbYAML (http://rbyaml.rubyforge.org/) 的纯 ruby​​ yaml 解析器,它起源于 JRuby,似乎没有这个错误:

    >> require 'rbyaml'
    => true
    >> message_text = <<END
    
      X
    X
    END
    => "\n  X\nX\n"
    >> yaml = RbYAML.dump(message_text)
    => "--- "\\n  X\\nX\\n"\n"
    >> RbYAML.load(yaml)
    => "\n  X\nX\n"
    >> 
    

    最后,您是否完全考虑过另一种序列化格式? Ruby 的 Marshal 库也没有这个 bug,而且比 Yaml 更快(参见 http://significantbits.wordpress.com/2008/01/29/yaml-vs-marshal-performance/):

    >> message_text = <<END
    
      X
    X
    END
    => "\n  X\nX\n"
    >> marshal = Marshal.dump(message_text)
    => "\004\b"\f\n  X\nX\n"
    >> Marshal.load(marshal)
    => "\n  X\nX\n"
    

    【讨论】:

    • 感谢您的洞察力。我仍然不确定该联系谁来解决这个问题,但是有一个 Github 项目 (github.com/indeyets/syck) 正在从旧的 _why 存储库进行。我在将 HAML HTML 输出序列化到 ActiveRecord 列时遇到了这个问题。巧合的是,HAML 使用与 YAML 相同的两个空格结构来格式化 HTML,并触发这种极端情况。
    • 您是否考虑过使用其他序列化方案,例如 Marshal?必须是yaml吗?
    • 我需要的是能够修复 ActiveRecord 序列化列的行为的东西。如果有办法使用 Marshal 代替,我完全赞成,但我还没有看到任何关于如何在没有严重的猴子补丁的情况下做到这一点的信息。
    【解决方案2】:

    您必须放弃简单的serializeActiveRecord::Base 方法才能这样做,但使用您自己的序列化方案并不难。 例如,序列化一些名为“person_data”的字段:

    class Person < ActiveRecord::Base
     def person_data
        self[:person_data] ? Marshal.load(self[:person_data]) : nil
      end
    
      def person_data=(x)
        self[:person_data] = Marshal.dump(x)
      end
    end
    
    ## User Person#person_data as normal and it is transparently marshalled
    p = Person.find 1
    p.person_data = {:color => "blue", :food => "vegetarian"}
    

    (查看ruby forum thread了解更多信息)

    【讨论】:

    • 看起来这可以从一些缓存中受益,但可以工作。谢谢!
    • 啊,好吧,只有当你真的用冻结的东西写信给你的领域时,这才有效。例如,分配一个稍后修改的哈希,只会保留初始状态。
    猜你喜欢
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 2014-01-08
    相关资源
    最近更新 更多