Search code examples
crystal-lang

Crystal - How to recursively change Hash values and save each change to new Hash


using this Hash object

{"foo" => {"bar" => 1, "baz" => 2}, "bla" => [1,2,3]}

I want to produce this array of Hash objects

[
  {"foo" => "*", "bla" => [1,2,3]},
  {"foo" => {"bar" => "*", "baz" => 2}, "bla" => [1,2,3]},
  {"foo" => {"bar" => "1", "baz" => "*"}, "bla" => [1,2,3]},
  {"foo" => {"bar" => "*", "baz" => 2}, "bla" => "*"}, 
]

Where I basically went over each key and changed its value to "*" while preserving the overall structure of the hash and saved the new hash produced in some array.

I have tried many ideas, but most just wont work as I can guess the Array type before, I only know this hash is produced by JSON.parse and then changed into Hash(String, JSON::Any)

My current try at it

hash = {"bar" => {"and" => "2", "br" => "1"}}
arr = [hash, {"bar" => "1"}]
arr.delete(arr.last)
arr.delete(hash)

def changer(hash, arr, original = nil)
  original = hash.dup
  hash.each do |k, v|
    if v.is_a?(Hash)
       changer(v, arr, hash)
    elsif v.is_a?(Array)
      v.each do |a|
        if a.is_a?(Hash)
            changer(a, arr, hash)
        end  
        end
  elsif v.is_a?(String) && original.is_a?(Hash(String, String))
      original[k.to_s] = "*"
      arr << original
    end
  end
end

Solution

  • Crystal v0.25.0 implements JSON::Any and YAML::Any without recursive aliases. With that change:

    require "json"
    
    hash = JSON.parse(%(
      {"foo": {"bar": 1, "baz": 2}, "bla": [1,2,3]}
    ))
    
    def changer(any : JSON::Any)
      result = [JSON::Any.new("*")]
      if (hash = any.as_h?)
        hash.each do |key, value|
          changer(value).each do |s|
            result << JSON::Any.new(hash.merge({key => s}))
          end
        end
      end
      result
    end
    
    puts changer(hash).join("\n")
    
    *
    {"foo" => "*", "bla" => [1_i64, 2_i64, 3_i64]}
    {"foo" => {"bar" => "*", "baz" => 2_i64}, "bla" => [1_i64, 2_i64, 3_i64]}
    {"foo" => {"bar" => 1_i64, "baz" => "*"}, "bla" => [1_i64, 2_i64, 3_i64]}
    {"foo" => {"bar" => 1_i64, "baz" => 2_i64}, "bla" => "*"}