0

Here's what I get in Rails params:

obj => {
    "raw_data" =>
        [
            { "id" => "1", "name" => "John Doe" },
            { "id" => "2", "name" => "Jane Doe" }
        ]
}

I have to transform into a following object:

obj => {
    "data" =>
        {
            "1" => { "name" => "John Doe" },
            "2" => { "name" => "Jane Doe" }
        }
}

Here's the code I have working so far:

if obj[:raw_data]
    obj[:data] = Hash.new
    obj[:raw_data].each do |raw|
      obj[:data][raw[:id]] = Hash.new
      obj[:data][raw[:id]][:name] = raw[:name] if raw[:name].present?
    end
end
obj.delete(:raw_data)

Is there a way to refactor it? Maybe using map. Note that data structure has to change from array to hash as well.

Thanks for any tips.

0

4 Answers 4

2

Here's one way:

obj = {
  "raw_data" => [
    { "id" => "1", "name" => "John Doe" },
    { "id" => "2", "name" => "Jane Doe" }
  ]
}

data = obj["raw_data"].map do |item|
  item = item.dup
  [ item.delete('id'), item ]
end

obj2 = { "data" => data.to_h }
# => { "data" =>
#      { "1" => { "name" => "John Doe" },
#        "2" => { "name" => "Jane Doe" }
#      }
#    }

If you're using Rails you can use the Hash#except method from ActiveSupport to make it a little more succinct:

data = obj["raw_data"].map {|item| [ item["id"], item.except("id") ] }
obj2 = { "data" => data.to_h }
Sign up to request clarification or add additional context in comments.

4 Comments

You are relying on the order of the array being the same as the id's which is a faulty assumption. [ { "id" => 5, "name" => "John Doe" } , { "id" => 15, "name" => "Jane Doe" } ] would give the wrong output.
I think you've misunderstood how this code works. The output it gives for that input is { 5 => { "name" => "John Doe" }, 15 => { "name" => "Jane Doe" } }. What output do you think it should give?
Ah, you're correct, missed the [ item.delete('id'), item ] part. Sorry
Perfect, thanks Jordan, I used the one with except. Short and sweet :)
1
d = obj[:raw_data]
keys = d.map { |h| h["id"] }
values = d.map { |h| h.except("id") }
Hash[ keys.zip(values) ]

# or as a oneliner
Hash[ d.map { |h| h["id"] }.zip(d.map { |h| h.except("id")) ]
# => {"1"=>{"name"=>"John Doe"}, "2"=>{"name"=>"Jane Doe"}}

This special Hash[] syntax lets you create a hash from a array of keys and an array of values.

Hash.except(*args) is an ActiveSupport addition to the hash class which returns a new key without the keys in the blacklist.

4 Comments

Why don't we have Hash#except in pure-Ruby? Does this have a counterpart include_only (to avoid the need for h.except(*(h.keys-include_keys)))?
Kitesurfing instructor? Decided to go for the big money, eh?
The inverse of Hash#except is Hash#slice which takes a whitelist instead of a blacklist, would be nice to have them in the stdlib. @CarySwoveland
There are other things to life than money :) @CarySwoveland
0

In rails, you can use index_by method:

obj = {raw_data: [{id: "1", name: "John Doe"}, {id: "2", name: "Jane Doe"}]}

obj2 = {
 data: obj[:raw_data].index_by {|h| h[:id]}.each {|_,h| h.delete(:id)}
} #=> {:data=>{"1"=>{:name=>"John Doe"}, "2"=>{:name=>"Jane Doe"}}}

One downfall of this is that it will modify the original data by deleting id property. If this is unacceptable, here is modified, safe version:

obj2 = {
 data: obj[:raw_data].map(&:clone).index_by {|h| h[:id]}.each {|_,h| h.delete(:id)}
} #=> {:data=>{"1"=>{:name=>"John Doe"}, "2"=>{:name=>"Jane Doe"}}}

2 Comments

This has the undesirable side effect of altering the original hash.
@max - True to that, up to OP to decide whether this is a fair trade-off for code simplicity in this case.
0

I assume you mean obj = {...} and not obj => {...}, as the latter is not a valid object. If so:

{ "data" => obj["raw_data"].each_with_object({}) { |g,h|
  h[g["id"]] = g.reject { |k,_| k == "id" } } }
  #=> {"data"=>{"1"=>{"name"=>"John Doe"}, "2"=>{"name"=>"Jane Doe"}}}

If obj can be mutated, you can simplify a bit:

{ "data" => obj["raw_data"].each_with_object({}) { |g,h| h[g.delete("id")]=g } }

As an improved non-mutating solution, @Max suggested a Rails' tweak:

{ "data" => obj["raw_data"].each_with_object({}) { |g,h| h[g["id"]] = g.except("id") } }

That looks good to me, but as I don't know rails, I'm taking that advice at face value.

1 Comment

I like it, in rails you might want to use h[g['id']] = g.except(:id) as its both succinct and side effect free.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.