Parsing Json in Ruby (Like Xpath)

Parsing Json in Ruby (Like Xpath)

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.

1

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.

2

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'].

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

David Miller
Author

David Miller

David Miller brings 15 years of experience in global economics, personal finance strategy, and market dynamics. He specializes in turning complex economic trends into actionable insights for everyday readers.