Rails:使用序列化哈希编码问题,尽管UTF8

时间:2011-12-19 07:01:14

标签: ruby-on-rails ruby postgresql utf-8 yaml

我刚刚从ruby 1.9.2更新到ruby 1.9.3p0(2011-10-30修订版33570)。我的rails应用程序使用postgresql作为其数据库后端。系统区域设置是UTF8,数据库编码也是如此。 rails应用程序的默认编码也是UTF8。我有中国用户输入汉字和英文字符。字符串存储为UTF8编码字符串。

Rails版本:3.0.9

由于更新,数据库中的一些现有中文字符串不再正确显示。这不会影响所有字符串,只会影响序列化哈希的一部分。存储为纯字符串的所有其他字符串看起来仍然是正确的。


示例:

这是一个序列化的哈希,在数据库中存储为UTF8字符串:

broken = "--- !map:ActiveSupport::HashWithIndifferentAccess \ncheckbox: \"1\"\nchoice: \"Round Paper Clips  \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"\ninfo: \"10\\xE7\\x9B\\x92\"\n"

为了将此字符串转换为ruby哈希,我使用YAML.load对其进行反序列化:

broken_hash = YAML.load(broken)

这会返回带有乱码内容的哈希:

{"checkbox"=>"1", "choice"=>"Round Paper Clips  ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089\r\n", "info"=>"10ç\u009B\u0092"}

乱码的东西应该是UTF8编码的中文。 broken_hash['info'].encoding告诉我,ruby认为这是#<Encoding:UTF-8>。我不同意。

有趣的是,之前没有序列化的所有其他字符串看起来都很好。在同一记录中,不同的字段包含看起来正确的中文字符---在rails控制台,psql控制台和浏览器中。

,每个字符串---无论是序列化哈希还是普通字符串---都保存到数据库中

我试图将乱码文本从可能错误的编码(如GB2312或ANSI)转换为UTF-8,尽管ruby声称这已经是UTF-8了,当然我失败了。这是我使用的代码:

require 'iconv'
Iconv.conv('UTF-8', 'GB2312', broken_hash['info'])

这失败了,因为ruby不知道如何处理字符串中的非法序列。

我真的只想运行一个脚本来修复所有旧的,可能是破坏的序列化哈希字符串并完成它。有没有办法将这些破碎的字符串转换成类似中文的东西?


我刚刚在原始字符串中使用编码的UTF-8字符串(在上例中称为“已损坏”)。这是以序列化字符串编码的中文字符串:

chinese = "\\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"

我注意到通过取消它(删除转义反斜杠)很容易将其转换为真正的UTF-8编码字符串。

chinese_ok = "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89\r\n"

返回正确的UTF-8编码中文字符串:"(回形针)\r\n"

只有当我使用YAML.load(...)将字符串转换为ruby哈希时,事情才会崩溃。也许我应该在将原始字符串输入YAML.load之前处理它。只是让我想知道为什么会这样......


有趣!这可能是由于YAML引擎“心理”现在在1.9.3中默认使用。我使用YAML::ENGINE.yamler = 'syck'切换到“syck”引擎,正确解析了断开的字符串。

2 个答案:

答案 0 :(得分:12)

这似乎是由两个可用的YAML引擎“syck”和“psych”的行为差异引起的。 要将YAML引擎设置为syck:

YAML::ENGINE.yamler = 'syck'

将YAML引擎设置回心理:

YAML::ENGINE.yamler = 'psych'

“syck”引擎按预期处理字符串,并将它们转换为具有适当中文字符串的哈希值。当使用“psych”引擎时(默认情况下为ruby 1.9.3),转换会产生乱码。

将上述行(两者中的第一行)添加到config/application.rb可修复此问题。 “syck”引擎不再被维护,所以我应该只使用这种解决方法给我一些时间让字符串可以接受“心理”。

答案 1 :(得分:9)

来自1.9.3 NEWS file

* yaml
  * The default YAML engine is now Psych. You may downgrade to syck by setting
    YAML::ENGINE.yamler = 'syck'.

显然,Syck和Psych YAML引擎以不同且不兼容的方式处理非ASCII字符串。

给出像你一样的哈希:

h = {
    "checkbox" => "1",
    "choice"   => "Round Paper Clips  (回形针)\r\n",
    "info"     => "10盒"
}

使用旧的Syck引擎:

>> YAML::ENGINE.yamler = 'syck'
>> h.to_yaml
=> "--- \ncheckbox: "1"\nchoice: "Round Paper Clips  \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n"\ninfo: "10\\xE7\\x9B\\x92"\n"

我们得到了您目前在数据库中的丑陋双反斜杠格式。切换到心理:

>> YAML::ENGINE.yamler = 'psych'
=> "psych"
>> h.to_yaml
=> "---\ncheckbox: '1'\nchoice: ! "Round Paper Clips  (回形针)\\r\\n"\ninfo: 10盒\n"

字符串保持正常的UTF-8格式。如果我们手动将编码搞砸为Latin-1:

>> Iconv.conv('UTF-8', 'ISO-8859-1', "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89") 
=> "ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089"
然后我们会得到你所看到的那种废话。

YAML文档相当薄,所以我不知道你是否可以强迫Psych理解旧的Syck格式。我认为你有三种选择:

  1. 使用旧的不受支持和已弃用的Syck引擎,在YAML任何内容之前,您需要YAML::ENGINE.yamler = 'syck'
  2. 使用Syck加载并解码所有YAML,然后使用Psych重新编码并保存。
  3. 停止使用serialize支持使用JSON(或其他一些稳定,可预测和可移植的文本格式)手动序列化/反序列化,或使用关联表,以便您根本不存储序列化数据。 / LI>