JsonPath ↑

This is an implementation of goessner.net/articles/JsonPath/.

What is JsonPath? ↑

JsonPath is a way of addressing elements within a JSON object. Similar to xpath of yore, JsonPath lets you traverse a json object and manipulate or access it.

Usage ↑

Command-line ↑

There is stand-alone usage through the binary jsonpath

jsonpath [expression] (file|string)

If you omit the second argument, it will read stdin, assuming one valid JSON
object per line. Expression must be a valid jsonpath expression.

Library ↑

To use JsonPath as a library simply include and get goin’!

require 'jsonpath'

json = <<-HERE_DOC
{"store":
  {"bicycle":
    {"price":19.95, "color":"red"},
    "book":[
      {"price":8.95, "category":"reference", "title":"Sayings of the Century", "author":"Nigel Rees"},
      {"price":12.99, "category":"fiction", "title":"Sword of Honour", "author":"Evelyn Waugh"},
      {"price":8.99, "category":"fiction", "isbn":"0-553-21311-3", "title":"Moby Dick", "author":"Herman Melville","color":"blue"},
      {"price":22.99, "category":"fiction", "isbn":"0-395-19395-8", "title":"The Lord of the Rings", "author":"Tolkien"}
    ]
  }
}
HERE_DOC

Now that we have a JSON object, let’s get all the prices present in the object. We create an object for the path in the following way.

path = JsonPath.new('$..price')

Now that we have a path, let’s apply it to the object above.

path.on(json)
# => [19.95, 8.95, 12.99, 8.99, 22.99]

Or reuse it later on some other object (thread safe) …

path.on('{"books":[{"title":"A Tale of Two Somethings","price":18.88}]}')
# => [18.88]

You can also just combine this into one mega-call with the convenient JsonPath.on method.

JsonPath.on(json, '$..author')
# => ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "Tolkien"]

Of course the full JsonPath syntax is supported, such as array slices

JsonPath.new('$..book[::2]').on(json)
# => [
#      {"price" => 8.95, "category" => "reference", "title" => "Sayings of the Century", "author" => "Nigel Rees"},
#      {"price" => 8.99, "category" => "fiction", "isbn" => "0-553-21311-3", "title" => "Moby Dick", "author" => "Herman Melville","color" => "blue"},
#    ]

…and evals, including those with conditional operators

JsonPath.new("$..price[?(@ < 10)]").on(json)
# => [8.95, 8.99]

JsonPath.new("$..book[?(@['price'] == 8.95 || @['price'] == 8.99)].title").on(json)
# => ["Sayings of the Century", "Moby Dick"]

JsonPath.new("$..book[?(@['price'] == 8.95 && @['price'] == 8.99)].title").on(json)
# => []

There is a convenience method, #first that gives you the first element for a JSON object and path.

JsonPath.new('$..color').first(json)
# => "red"

As well, we can directly create an Enumerable at any time using #[].

enum = JsonPath.new('$..color')[json]
# => #<JsonPath::Enumerable:...>
enum.first
# => "red"
enum.any?{ |c| c == 'red' }
# => true

For more usage examples and variations on paths, please visit the tests. There are some more complex ones as well.

Querying ruby data structures ↑

If you have ruby hashes with symbolized keys as input, you can use :use_symbols to make JsonPath work fine on them too:

book = { title: "Sayings of the Century" }

JsonPath.new('$.title').on(book)
# => []

JsonPath.new('$.title', use_symbols: true).on(book)
# => ["Sayings of the Century"]

JsonPath also recognizes objects responding to dig (introduced in ruby 2.3), and therefore works out of the box with Struct, OpenStruct, and other Hash-like structures:

book_class = Struct.new(:title)
book = book_class.new("Sayings of the Century")

JsonPath.new('$.title').on(book)
# => ["Sayings of the Century"]

JsonPath is able to query pure ruby objects and uses __send__ on them. The option is enabled by default in JsonPath 1.x, but we encourage to enable it explicitly:

book_class = Class.new{ attr_accessor :title }
book = book_class.new
book.title = "Sayings of the Century"

JsonPath.new('$.title', allow_send: true).on(book)
# => ["Sayings of the Century"]

Other available options ↑

By default, JsonPath does not return null values on unexisting paths. This can be changed using the :default_path_leaf_to_null option

JsonPath.new('$..book[*].isbn').on(json)
# => ["0-553-21311-3", "0-395-19395-8"]

JsonPath.new('$..book[*].isbn', default_path_leaf_to_null: true).on(json)
# => [nil, nil, "0-553-21311-3", "0-395-19395-8"]

When JsonPath returns a Hash, you can ask to symbolize its keys using the :symbolize_keys option

JsonPath.new('$..book[0]').on(json)
# => [{"category" => "reference", ...}]

JsonPath.new('$..book[0]', symbolize_keys: true).on(json)
# => [{category: "reference", ...}]

Selecting Values ↑

It’s possible to select results once a query has been defined after the query. For example given this JSON data:

{
    "store": {
        "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99
            }
        ]
}

… and this query:

"$.store.book[*](category,author)"

… the result can be filtered as such:

[
   {
      "category" : "reference",
      "author" : "Nigel Rees"
   },
   {
      "category" : "fiction",
      "author" : "Evelyn Waugh"
   }
]

Manipulation ↑

If you’d like to do substitution in a json object, you can use #gsub or #gsub! to modify the object in place.

JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash

The result will be

{'candy' => 'big turks'}

If you’d like to remove all nil keys, you can use #compact and #compact!. To remove all keys under a certain path, use #delete or #delete!. You can even chain these methods together as follows:

json = '{"candy":"lollipop","noncandy":null,"other":"things"}'
o = JsonPath.for(json).
  gsub('$..candy') {|v| "big turks" }.
  compact.
  delete('$..other').
  to_hash
# => {"candy" => "big turks"}

Fetch all paths ↑

To fetch all possible paths in given json, you can use fetch_all_paths method.

data:

{
    "store": {
        "book": [
            {
                "category": "reference",
                "author": "Nigel Rees"
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh"
            }
        ]
}

… and this query:

JsonPath.fetch_all_path(data)

… the result will be:

["$", "$.store", "$.store.book", "$.store.book[0].category", "$.store.book[0].author", "$.store.book[0]", "$.store.book[1].category", "$.store.book[1].author", "$.store.book[1]"]

Contributions ↑

Please feel free to submit an Issue or a Pull Request any time you feel like you would like to contribute. Thank you!

Running an individual test ↑

ruby -Ilib:../lib test/test_jsonpath.rb --name test_wildcard_on_intermediary_element_v6