iamse7en
iamse7en

Reputation: 609

Ruby: Extract elements from deeply nested JSON structure based on criteria

Want to extract every marketID from every market that has a marketName == 'Moneyline'. Tried a few combinations of .maps, .rejects, and/or .selects but can't narrow it down as the complicated structure is confusing me.

There are many markets in events, and there are many events as well. A sample of the structure (tried to edit it for brevity):

{"currencyCode"=>"GBP",
"eventTypes"=>[
    {"eventTypeId"=>6423,
    "eventNodes"=>[
        {"eventId"=>28017227,
        "event"=>
            {"eventName"=>"Philadelphia @ Seattle"
            },
            "marketNodes"=>[
                {"marketId"=>"1.128274650",
                "description"=>
                    {"marketName"=>"Moneyline"}
                },
                {"marketId"=>"1.128274625",
                "description"=>
                    {"marketName"=>"Winning Margin"}
                }}}]},
        {"eventId"=>28018251,
        "event"=>
            {"eventName"=>"Arkansas @ Mississippi State"
            },
            "marketNodes"=>[
                {"marketId"=>"1.128299882",
                "description"=>
                    {"marketName"=>"Under/Over 60.5pts"}
                },
                {"marketId"=>"1.128299881",
                "description"=>
                    {"marketName"=>"Moneyline"}
                }}}]},
        {"eventId"=> etc....

Tried all kinds of things, for example,

markets = json["eventTypes"].first["eventNodes"].map {|e| e["marketNodes"].map { |e| e["marketId"] } if (e["marketNodes"].map {|e| e["marketName"] == 'Moneyline'})}
markets.flatten
# => yields every marketId not every marketId with marketName of 'Moneyline'

Getting a simple array with every marketId from Moneyline markets with no other information is sufficient. Using Rails methods is fine too if preferred.

Sorry if my editing messed up the syntax. Here's the source. It looks like this only with => instead of : after parsing the JSON.

Thank you!

Upvotes: 1

Views: 1328

Answers (2)

Eric Duminil
Eric Duminil

Reputation: 54263

Just for fun, here's another possible answer, this time with regexen. It is shorter but might break depending on your input data. It reads the json data directly as String :

json = File.read('data.json')

market_ids   = json.scan(/(?<="marketId":")[\d\.]+/)
market_names = json.scan(/(?<="marketName":")[^"]+/)

moneyline_market_ids = market_ids.zip(market_names).select{|id,name| name=="Moneyline"}.map{|id,_| id}
puts moneyline_market_ids.join(', ')
#=> 1.128255531, 1.128272164, 1.128255516, 1.128272159, 1.128278718, 1.128272176, 1.128272174, 1.128272169, 1.128272148, 1.128272146, 1.128255464, 1.128255448, 1.128272157, 1.128272155, 1.128255499, 1.128272153, 1.128255484, 1.128272150, 1.128255748, 1.128272185, 1.128278720, 1.128272183, 1.128272178, 1.128255729, 1.128360712, 1.128255371, 1.128255433, 1.128255418, 1.128255403, 1.128255387

It outputs the same result as the other answer.

Upvotes: 1

Eric Duminil
Eric Duminil

Reputation: 54263

I love nested maps and selects :D

require 'json'

hash = JSON.parse(File.read('data.json'))

moneyline_market_ids = hash["eventTypes"].map{|type|
  type["eventNodes"].map{|node|
    node["marketNodes"].select{|market|
      market["description"]["marketName"] == 'Moneyline'
    }.map{|market| market["marketId"]}
  }
}.flatten

puts moneyline_market_ids.join(', ')
#=> 1.128255531, 1.128272164, 1.128255516, 1.128272159, 1.128278718, 1.128272176, 1.128272174, 1.128272169, 1.128272148, 1.128272146, 1.128255464, 1.128255448, 1.128272157, 1.128272155, 1.128255499, 1.128272153, 1.128255484, 1.128272150, 1.128255748, 1.128272185, 1.128278720, 1.128272183, 1.128272178, 1.128255729, 1.128360712, 1.128255371, 1.128255433, 1.128255418, 1.128255403, 1.128255387

Upvotes: 2

Related Questions