Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Ruby, how to be warned of duplicate keys in hashes when loading a YAML document?

Tags:

ruby

yaml

In the following Ruby example, is there a mode to have YAML NOT silently ignore the duplicate key 'one'?

irb(main):001:0> require 'yaml'
=> true
irb(main):002:0> str = '{ one: 1, one: 2 }'
=> "{ one: 1, one: 2 }"
irb(main):003:0> YAML.load(str)
=> {"one"=>2}

Thanks!

like image 951
TomJam Avatar asked Feb 06 '11 02:02

TomJam


People also ask

Does YAML allow duplicate keys?

Duplicate keys in YAML files are not allowed in the spec (https://yaml.org/spec/1.2.2/#nodes, https://yaml.org/spec/1.0/#model-node), but the older version of symfony/yaml does not complain about them. The newer version throws an exception.

What is Ruby YAML used for?

We can use YAML files in conjunction with our Ruby program to store objects, and in the case of web browsing, maintain state by storing data that persists in external files. Additionally, by overwriting YAML files, we can modify the values inside of our stored Ruby objects.


1 Answers

Using Psych, you can traverse the AST tree to find duplicate keys. I'm using the following helper method in my test suite to validate that there are no duplicate keys in my i18n translations:

def duplicate_keys(file_or_content)
  yaml = file_or_content.is_a?(File) ? file_or_content.read : file_or_content
  duplicate_keys = []

  validator = ->(node, parent_path) do
    if node.is_a?(Psych::Nodes::Mapping)
      children = node.children.each_slice(2) # In a Mapping, every other child is the key node, the other is the value node.
      duplicates = children.map { |key_node, _value_node| key_node }.group_by(&:value).select { |_value, nodes| nodes.size > 1 }

      duplicates.each do |key, nodes|
        duplicate_key = {
          file: (file_or_content.path if file_or_content.is_a?(File)),
          key: parent_path + [key],
          occurrences: nodes.map { |occurrence| "line: #{occurrence.start_line + 1}" },
        }.compact

        duplicate_keys << duplicate_key
      end

      children.each { |key_node, value_node| validator.call(value_node, parent_path + [key_node.try(:value)].compact) }
    else
      node.children.to_a.each { |child| validator.call(child, parent_path) }
    end
  end

  ast = Psych.parse_stream(yaml)
  validator.call(ast, [])

  duplicate_keys
end
like image 65
user3592693 Avatar answered Sep 19 '22 04:09

user3592693