0

So basically have a structure like this:

{
    "i$tems": {
        "it$em": [
            {
                "batt$ers": {
                    "ba$tter": [
                        {
                            "i$d": "1001",
                            "t$ype": "Regular"
                        },
                        {
                            "i$d": "1002",
                            "type": "Chocolate"
                        },
                        {
                            "i$d": "1003",
                            "t$ype": "Blueberry"
                        },
                        {
                            "i$d": "1004",
                            "t$ype": "Devil's Food"
                        }
                    ]
                },
                "$id": "0001",
                "$name": "Cake",
                "$ppu": 0.55,
                "$topping": [
                    {
                        "i$d": "5001",
                        "t$ype": "None"
                    },
                    {
                        "i$d": "5002",
                        "t$ype": "Glazed"
                    },
                    {
                        "i$d": "5005",
                        "t$ype": "Sugar"
                    },
                    {
                        "i$d": "5007",
                        "t$ype": "Powdered Sugar"
                    },
                    {
                        "i$d": "5006",
                        "t$ype": "Chocolate with Sprinkles"
                    },
                    {
                        "i$d": "5003",
                        "t$ype": "Chocolate"
                    },
                    {
                        "i$d": "5004",
                        "t$ype": "Maple"
                    }
                ],
                "ty$pe": "donut"
            }
        ]
    } 
}

and want something like this:

{
    "items": {
        "item": [
            {
                "batters": {
                    "batter": [
                        {
                            "id": "1001",
                            "type": "Regular"
                        },
                        {
                            "id": "1002",
                            "type": "Chocolate"
                        },
                        {
                            "id": "1003",
                            "type": "Blueberry"
                        },
                        {
                            "id": "1004",
                            "type": "Devil's Food"
                        }
                    ]
                },
                "id": "0001",
                "name": "Cake",
                "ppu": 0.55,
                "topping": [
                    {
                        "id": "5001",
                        "type": "None"
                    },
                    {
                        "id": "5002",
                        "type": "Glazed"
                    },
                    {
                        "id": "5005",
                        "type": "Sugar"
                    },

                    {
                        "id": "5007",
                        "type": "Powdered Sugar"
                    },
                    {
                        "id": "5006",
                        "type": "Chocolate with Sprinkles"
                    },
                    {
                        "id": "5003",
                        "type": "Chocolate"
                    },
                    {
                        "id": "5004",
                        "type": "Maple"
                    }
                ],
                "type": "donut"
            }
        ]
    }
}

in Ruby

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Rico
  • 58,485
  • 12
  • 111
  • 141

3 Answers3

4

Here's a generic map_keys function that recursively runs some block on a hash's keys.

def map_keys(hash, &blk)
  hash.each_with_object({}) do |(key, val), new_hash|        
    new_hash[blk.call(key)] = if val.is_a?(Hash)
      map_keys(val, &blk)
    else
      val
    end
  end
end

It returns a new hash with modified keys. In your case you could use it like this:

new_data = map_keys(data) do |key|
  key.gsub("$", '')
end

editing

just noticed you have arrays in your data. Here's a modified version that handles that:

def map_keys(hash_or_array, &blk)
  if hash_or_array.is_a?(Hash)
    hash = hash_or_array
    hash.each_with_object({}) do |(key, val), new_hash|        
      new_hash[blk.call(key)] = if [Array, Hash].include?(val.class)
        map_keys(val, &blk)
      else
        val
      end
    end
  elsif hash_or_array.is_a?(Array)
    array = hash_or_array
    array.map { |elem| map_keys(elem, &blk) }
  end
end

by the way, modifying this to work on values instead of keys is easy:

def map_values(hash_or_array, &blk)
  if hash_or_array.is_a?(Hash)
    hash = hash_or_array
    hash.each_with_object({}) do |(key, val), new_hash|        
      new_hash[key] = if [Array, Hash].include?(val.class)
        map_keys(val, &blk)
      else
        blk.call val
      end
    end
  elsif hash_or_array.is_a?(Array)
    array = hash_or_array
    array.map { |elem| map_keys(elem, &blk) }
  end
end
max pleaner
  • 26,189
  • 9
  • 66
  • 118
2

If the only dollar sign characters to be removed are those in the names of keys, be they symbols or strings, you could do the following.

def clean_up(obj)
  case obj
  when Hash
    obj.each_with_object({}) do |(k,v),h|
      key =
      case k
      when String then k.delete('$')
      when Symbol then k.to_s.delete('$').to_sym
      else k
      end
      h[key] = clean_up(v)
    end
  when Array
    obj.each_with_object([]) { |e,a| a << clean_up(e) }
  else
    obj
  end
end

Suppose

h =
{ "i$tems": {
    "it$em": [
      { "batt$ers": {
          "ba$tter": [
            { "i$d": "1001", "t$ype": "Regular" },
            { "i$d": "1002", type: "Choc$olate" }
          ]
        },
        "i$d": "0001",
        42=>[
          { "i$d": "5001", "t$ype": "None" }, 
          { "str$ing"=>"5002", "t$ype": "Glazed"}
        ],
        "ty$pe": "donut"
      }
    ]
  }
}

Note that I've simplified the hash in the question and also changed some keys and values. Then,

clean_up h
  #=> { :items=>{
          :item=>[
            { :batters=>{
                :batter=>[
                  { :id=>"1001", :type=>"Regular" },
                  { :id=>"1002", :type=>"Choc$olate" }
                ]
              },
              :id=>"0001",
              42=>[
                { :id=>"5001", :type=>"None" },
                { "string"=>"5002", :type=>"Glazed" }
              ],
              :type=>"donut"
            }
          ]
        }
      }

If it is guaranteed that (as in the example) the only symbols or strings containing dollar signs are hash keys, you could simplify to the following.

require 'json'

JSON.parse(h.to_json.delete('$'))
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • So this basically works only if the keys only have '$' but what if the values have $ and you don't want to remove it from them. – Rico Apr 25 '17 at 04:23
  • @Rico, in that case, go directly to square #1, [do not collect $200](https://en.wikipedia.org/wiki/Do_not_pass_Go._Do_not_collect_$200.). – Cary Swoveland Apr 25 '17 at 04:26
  • @Rico, I modified my answer to consider the case where values that are symbols or string may contain dollar sign characters, and those characters are not to be removed. – Cary Swoveland Apr 25 '17 at 06:40
1

Basically I came up with this code:

#!/usr/bin/env ruby

require 'yaml'
require 'json'

def iterate(data)
  if data.is_a?(Array)
    return data.map { |i| iterate(i) }
  elsif data.is_a?(Hash)
    new_h = Hash[data]
    data.each do |k, v|
      if k =~ /\$/
        new_key = k.delete("\$")
        new_h.delete(k)
        if v.is_a?(Array) || v.is_a?(Hash)
          new_h[new_key] = iterate(v)
        else
          new_h[new_key] = v
        end
      else
        if v.is_a?(Array) || v.is_a?(Hash)
          new_h[k] = iterate(v)
        else
          new_h[k] = v
        end
      end
    end
    return new_h
  end
end

data = YAML.load(File.read('nested.json'))
puts JSON.dump(iterate(data))
Rico
  • 58,485
  • 12
  • 111
  • 141