sp1117
sp1117

Reputation: 69

Ruby array construction

I have the following json,

{
    "items": [
        {
            "name": "table",
            "item_group": [
                {
                    "code": "code1",
                    "section": [
                        {                         
                            "unit": "centimeter",
                            "values": [
                                {
                                    "display": "151.13 centimeter"
                                }
                            ]
                        },
                        {                         
                            "unit": "centimeter (qualifier value)",
                            "values": [
                                {
                                    "display": "170.39 centimeter"
                                }
                            ]
                        }
                    ],
                    "total": 2
                }
            ],
            "more_results": false,
            "total": 1
        }
    ]
}

How do I iterate through this and create an array of "values". I want something like [151.13 centimeter, 170.39 centimeter,....]. Please show some direction in this. thanks.

Note: I have to do this in Haml.

Upvotes: 2

Views: 183

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Assuming you know the structure of the underlying hash, you can do this:

Code

require 'json'

JSON.parse(json_str)['items'].each_with_object([]) do |g,arr|
  g['item_group'].each do |gg|
    gg['section'].each do |ggg|
      ggg['values'].each { |gggg|arr << gggg['display'][/\d+\.\d+/].to_f }
    end
  end
end    
  #=> [151.13, 170.39] 

Explanation

The steps are as follows:

a = JSON.parse(json_str)
b = a['items']
enum = b.each_with_object([])
  #=> #<Enumerator: [{"name"=>"table",..."total"=>1}]:each_with_object([])> 

We can see the elements of the enumerator by converting it to an array:

enum.to_a
  #=> [[{"name"=>"table", "item_group"=>[{"code"=>"code1",
  #        "section"=>[{"unit"=>"centimeter",
  #        "values"=>[{"display"=>"151.13 centimeter"}]},
  #        { "unit"=>"centimeter (qualifier value)",
  #        "values"=>[{"display"=>"170.39 centimeter"}]}], "total"=>2}],
  #     "more_results"=>false,
  #     "total"=>1},
  #   []]] 

Notice that this array has a single element, a two-element array containing a hash and an empty array.

The first element of enum is passed to the block and assigned to the block variables using parallel assignment:

g, arr = enum.next
  #=> {"name"=>"table",
  #    "item_group"=>[
  #      {"code"=>"code1",
  #      "section"=>[
  #        {"unit"=>"centimeter",
  #         "values"=>[
  #           {"display"=>"151.13 centimeter"}
  #         ]
  #        },
  #      {"unit"=>"centimeter (qualifier value)",
  #      "values"=>[
  #        {"display"=>"170.39 centimeter"}
  #       ]
  #      }
  #     ],
  #    "total"=>2}],
  #    "more_results"=>false,
  #    "total"=>1} 
arr #=> [] 

c = g['item_group']
  #=> [{"code"=>"code1",
  #     "section"=>[
  #       {"unit"=>"centimeter",
  #        "values"=>[
  #          {"display"=>"151.13 centimeter"}
  #         ]
  #       },
  #       {"unit"=>"centimeter (qualifier value)",
  #        "values"=>[
  #          {"display"=>"170.39 centimeter"}
  #        ]
  #       }
  #   ],
  #   "total"=>2}] 

Note c has the form [hash].

The first (and only) element of c is passed to its block and assigned to its block variable:

gg = c.first
d = gg['section']
  #=> [{"unit"=>"centimeter",
        "values"=>[
         {"display"=>"151.13 centimeter"}
        ]
       },
       {"unit"=>"centimeter (qualifier value)",
        "values"=>[
          {"display"=>"170.39 centimeter"}
        ]
       }
      ] 

ggg = d.first
  #=> {"unit"=>"centimeter",
  #    "values"=>[
  #      {"display"=>"151.13 centimeter"}
  #    ]
  #   } 

e = ggg['values']
  #=> [{"display"=>"151.13 centimeter"}]

gggg = e.first
  #=> {"display"=>"151.13 centimeter"} 

f = gggg['display']
  #=> "151.13 centimeter" 
g = f[/\d+\.\d+/]
  #=> "151.13" 
i = g.to_f
  #=> 151.13 
arr << i
  #=> [151.13] 
arr
  #=> [151.13] 

The remaining calculations are similar.

Alternative expression

If, as in the example, the arrays JSON.parse(json_str)['items'], g['item_group'] and ggg['values'] each contain a single element (a hash), you could instead write:

JSON.parse(json_str)['items'].first['item_group'].first['section'].
  each_with_object([]) do |g, arr|
    arr << g['values'].first['display'][/\d+\.\d+/].to_f
  end
  #=> [151.13, 170.39] 

though I doubt that would be significantly more efficient.

Upvotes: 1

Amit Badheka
Amit Badheka

Reputation: 2672

I know there are multiple ways of doing this. But if you really have many complex JSON queries, you can try ruby-jq gem. It is very fast, as it uses linux pipes

require 'jq'
require 'jq/extend'
json_content =         {
        "items": [
            {
                "name": "table",
                "item_group": [
                {
                        "code": "code1",
                        "section": [
                            {                         
                                "unit": "centimeter",
                                "values": [
                                    {
                                        "display": "151.13 centimeter"
                                    }
                                ]
                            },
                            {                         
                                "unit": "centimeter (qualifier value)",
                                "values": [
                                    {
                                        "display": "170.39 centimeter"
                                    }
                                ]
                            }
                        ],
                        "total": 2
                    }
                ],
                "more_results": false,
                "total": 1
            }
        ]
    }


jq_filter = ' .items | .[].item_group |.[].section| . [ ] .values | . [ ] .display'

final_array = json_content.jq(jq_filter)

  # => final_array =  ["151.13 centimeter", "170.39 centimeter"]

Upvotes: 1

Related Questions