Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby recursive map of a hash of objects

Tags:

ruby

I have a hash that has an unknown collection and mixture of nested arrays, hashes, arrays of hashes and strings. This is the result of JSON.parse. The structure of the data must be the same as it started with. The end goal is to convert strings to Fixnums that could be Fixnums.

The following works just fine, but I was wondering if it could be shortened. Note how I need the key and the value in the clean method as not all strings that can be Fixnums should be. Any ideas?

def clean_node(node)
    if node.class == String
      clean(node)
    elsif node.class == Array
      node.each_with_index do |obj, i|
        if obj.class == String
          node[i] = clean(node[i], obj)
        else
          clean_node(obj)
        end
      end
    elsif node.class == Hash
      node.each_pair do |key, value|
        if value.class == String
          node[key] = clean(key, value)
        else
          clean_node(value)
        end
      end
    end
end

def clean(key, value)
    FIXNUM_KEYS.include?(key)? value.to_i : value
end
like image 501
Marc Avatar asked Nov 19 '25 05:11

Marc


1 Answers

There is no need to split up the string processing, you can do it all in one recursive routine, if you return the value of node at the end of the routine. This has the side effect of removing some parameters, and you can use in-place .map! for handling arrays.

Using case makes the selection by type slightly easier to read.

Adding the ability to deal with arrays of strings could be done in lots of ways. I've chosen to add a state variable (a common pattern in recursion, for instance to count depth). Then in the case of arrays, the recursion inherits the state from the current level, whilst the hash case determines new state (to convert or not) based on lookup in the list of keys to apply conversions.

def clean_node( node, convert_item = false )

    case node

    when String
      node = node.to_i if convert_item

    when Array
      node.map! do |obj| 
        clean_node( obj, convert_item )
      end

    when Hash
      node.each_pair do |key, value|
        node[key] = clean_node( value, FIXNUM_KEYS.include?(key) )
      end
    end

    node
end
like image 101
Neil Slater Avatar answered Nov 21 '25 18:11

Neil Slater