Flattening hash into string in Ruby

65,435

Solution 1

I wouldn't override .flatten, which is already defined:

Returns a new array that is a one-dimensional flattening of this hash. That is, for every key or value that is an array, extract its elements into the new array. Unlike Array#flatten, this method does not flatten recursively by default. If the optional level argument determines the level of recursion to flatten.

This is simplest way to do it that I'm aware of:

{:a => 100, :b => 200}.map{|k,v| "#{k}=#{v}"}.join('&')
# => "a=100&b=200"

Solution 2

Slight variation of @elektronaut's version:

You can actually put just an |e| there instead of |k, v| in which case e is an array of two elements and you can call e.join('='). Altogether you have something like

class Hash
  def join(keyvaldelim=$,, entrydelim=$,) # $, is the global default delimiter
    map {|e| e.join(keyvaldelim) }.join(entrydelim)
  end
end

{a: 100, b: 200}.join('=', '&') # I love showing off the new Ruby 1.9 Hash syntax
# => 'a=100&b=200'

Solution 3

If you're trying to generate a url query string, you most certainly should use a method like activesupport's to_param (aliased to to_query). Imagine the problems if you had an ampersand or equal sign in the data itself.

to_query takes care of escaping:

>> require 'active_support/core_ext/object'
>> {'a&' => 'b', 'c' => 'd'}.to_query
>> => "a%26=b&c=d"

EDIT

@fahadsadah makes a good point about not wanting to load Rails. Even active_support/core_ext/object will load 71 files. It also monkey-patches core classes.

A lightweight and cleaner solution:

require 'rack'  # only loads 3 files
{'a&' => 'b', 'c' => 'd'}.map{|pair|
  pair.map{|e| Rack::Utils.escape(e.to_s) }.join('=')
}.join('&')
# => "a%26=b&c=d"

It's important to escape, otherwise the operation isn't reversible.

Solution 4

Well, you could do it with standard methods and arrays:

class Hash
  def flatten(keyvaldelimiter, entrydelimiter)
    self.map { |k, v| "#{k}#{keyvaldelimiter}#{v}" }.join(entrydelimiter)
  end
end

You might also be interested in the Rails to_query method.

Also, obviously, you can write "#{k}#{keyvaldelimiter}#{v}" as k.to_s + keyvaldelimiter + v.to_s...

Solution 5

Not sure if there's a built-in way, but here's some shorter code:

class Hash
  def flatten(kvdelim='', entrydelim='')
    self.inject([]) { |a, b| a << b.join(kvdelim) }.join(entrydelim)
  end
end

puts ({ :a => :b, :c => :d }).flatten('=', '&') # => a=b&c=d
Share:
65,435
Fahad Sadah
Author by

Fahad Sadah

Updated on March 25, 2020

Comments

  • Fahad Sadah
    Fahad Sadah over 4 years

    Is there a way to flatten a hash into a string, with optional delimiters between keys and values, and key/value pairs?

    For example, print {:a => :b, :c => :d}.flatten('=','&') should print a=b&c=d

    I wrote some code to do this, but I was wondering if there was a neater way:

    class Hash
      def flatten(keyvaldelimiter, entrydelimiter)
        string = ""
        self.each do
          |key, value|
          key = "#{entrydelimiter}#{key}" if string != "" #nasty hack
          string += "#{key}#{keyvaldelimiter}#{value}"  
        end
        return string
      end
    end
    
    print {:a => :b, :c => :d}.flatten('=','&') #=> 'c=d&a=b'
    

    Thanks