比较ruby中的YAML文件而不加载

时间:2014-06-13 14:03:05

标签: ruby yaml

我正在寻找一种方法来比较(以及最好的情况,也是差异)Ruby中的两个YAML文件;无论关键顺序如何,自然而然。到目前为止,我发现的所有解决方案都依赖于使用YAML::load_file()加载文件。但是,我不能这样做,因为文件是我没有的类声明的Ruby对象的转储,因此加载它们会抛出undefined class/module

我认为我需要将它们作为字符串哈希加载并进行比较,但是如何告诉Ruby忽略类型信息并将其包含在比较中呢?

基于评论:我基本上对基于文本的比较感兴趣,但它必须知道"深度"的数据结构。例如,这是我所拥有的一个文件的摘录:

attributes: !ruby/hash:Example::Attributes
  !binary "b2NjaQ==": !ruby/hash:Example::Attributes
    !binary "Y29yZQ==": !ruby/hash:Example::Attributes
      !binary "aWQ=": !ruby/object:Example::Properties
        type: string
        required: false
        mutable: false
      !binary "dGl0bGU=": !ruby/object:Example::Properties
        type: string
        required: false
        mutable: false

因此,即使两个属性的顺序相反,比较也必须能够识别匹配。

1 个答案:

答案 0 :(得分:2)

Psych,Ruby的Yaml解析器,提供了几种检查Yaml数据的方法。 highest level加载Yaml并提供Ruby数据结构。这是查看Yaml标记并尝试加载适当的Ruby类的API,这会导致您的问题。它还会查看数据的格式,并在匹配时将其转换为各种类型(例如日期)。

next level将解析Yaml并为您提供包含“原始”Yaml数据的AST。高级API通过首先解析到此AST然后使用visitor pattern遍历它来创建Ruby数据(通常是哈希或数组)来工作。不幸的是,它没有提供这两个级别之间的任何内容,但是创建一个创建简化数据结构的解析器相当容易。

其核心Yaml数据基本上由标量(基本上是字符串),映射(散列)和序列(数组)组成 - 所有这些都可以有与之关联的标记。 Psych提供的AST由这三种类型(以及其他几种类型)组成,我们可以创建自己的访问者遍历它并生成一个仅由哈希,数组和字符串组成的Ruby结构。

这是基于Psych ToRuby visitor class的松散基础,但它不是试图将数据转换为适当的Ruby类型,而是仅创建数组,哈希和字符串,丢弃标记中的任何数据:

require 'psych'

class ToPlain < Psych::Visitors::Visitor

  # Scalars are just strings.
  def visit_Psych_Nodes_Scalar o
    o.value
  end

  # Sequences are arrays.
  def visit_Psych_Nodes_Sequence o
    o.children.each_with_object([]) do |child, list|
      list << accept(child)
    end
  end

  # Mappings are hashes.
  def visit_Psych_Nodes_Mapping o
    o.children.each_slice(2).each_with_object({}) do |(k,v), h|
      h[accept(k)] = accept(v)
    end
  end

  # We also need to handle documents...
  def visit_Psych_Nodes_Document o
    accept o.root
  end

  # ... and streams.
  def visit_Psych_Nodes_Stream o
    o.children.map { |c| accept c }
  end

  # Aliases aren't handles here :-(
  def visit_Psych_Nodes_Alias o
    # Not implemented!
  end

end

(注意这不会处理别名。添加对它们的支持并不太难,看看ToRuby做了什么,特别是register method以及它是如何使用的。)

你可以这样使用:

# Could also use parse_stream or parse_file here
ast = YAML.parse(my_data)
data = ToPlain.new.accept(ast)
# data now consists of just arrays, hashes and strings

如果在示例数据上使用此结果,则结果是一个如下所示的哈希:

{
  "attributes"=>{
    "b2NjaQ=="=>{
      "Y29yZQ=="=>{
        "aWQ="=>{
          "type"=>"string",
          "required"=>"false",
          "mutable"=>"false"
        },
        "dGl0bGU="=>{
          "type"=>"string",
          "required"=>"false",
          "mutable"=>"false"
        }
      }
    }
  }
}

虽然由于您使用的是二进制数据,因此键很不实用,但您仍然可以进行这样的比较:

occi_core_id    = data["attributes"]["b2NjaQ=="]["Y29yZQ=="]["aWQ="]
occi_core_title = data["attributes"]["b2NjaQ=="]["Y29yZQ=="]["dGl0bGU="]

puts occi_core_id == occi_core_title