Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing JSON in Ruby (like XPATH)

Tags:

json

ruby

I have a JSON document returned from a query to the Google Books API, e.g:

{ "items": [ { "volumeInfo": { "industryIdentifiers": [ { "type": "OTHER", "identifier": "OCLC:841804665" } ] } }, { "volumeInfo": { "industryIdentifiers": [ { "type": "ISBN_10", "identifier": "156898118X"...

I need the ISBN number (type: ISBN_10 or ISBN_13) and I've written a simple loop that traverses the parsed JSON (parsed = json.parse(my_uri_response)). In this loop, I have a next if k['type'] = "OTHER" which sets "type" to "OTHER".

How do I best extract just one ISBN number from my JSON example? Not all of them, just one.

Something like XPath search would be helpful.

like image 635
jcarpio Avatar asked Jun 27 '26 06:06

jcarpio


2 Answers

JSONPath may be just what you're looking for:

require 'jsonpath'

json = #your raw JSON above

path = JsonPath.new('$..industryIdentifiers[?(@.type == "ISBN_10")].identifier')

puts path.on(json)

Result:

156898118X

See this page for how XPath translates to JSONPath. It helped me determine the JSONPath above.

like image 158
Mark Thomas Avatar answered Jun 29 '26 21:06

Mark Thomas


how about:

parsed['items'].map { |book| 
  book['volume_info']['industryIdentifiers'].find{ |prop| 
    ['ISBN_10', 'ISBN_13'].include? prop['type']
  }['identifier'] 
}

If you receive undefined method [] for nil:NilClass this means that you have an element within items array, which has no volume_info key, or that you have a volume with a set of industryIdentifiers without ISBN. Code below should cover all those cases (+ the case when you have volumeInfo without industry_identifiers:

parsed['items'].map { |book| 
  identifiers = book['volume_info'] && book['volume_info']['industryIdentifiers']
  isbn_identifier = idetifiers && identifiers.find{ |prop| 
    ['ISBN_10', 'ISBN_13'].include? prop['type']}['identifier']
  }
  isbn_identifier && isbn_identifier['identifier']
}.compact

If you happen to have the andand gem, this might be written as:

parsed['items'].map { |book| 
  book['volume_info'].andand['industryIdentifiers'].andand.find{ |prop| 
    ['ISBN_10', 'ISBN_13'].include? prop['type']
  }.andand['identifier'] 
}.compact

Note that this will return only one ISBN for each volume. If you have volumes with both ISBN_10 and ISBN_13 and you want to get both, instead of find you'll need to use select method and .map{|i| i[:identifier]} in place of .andand['identifier'].

like image 23
BroiSatse Avatar answered Jun 29 '26 21:06

BroiSatse



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!