This is an implementation of http://goessner.net/articles/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.
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.
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 on some other object ...
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", "author"=>"Nigel Rees", "title"=>"Sayings of the Century"},
# {"price"=>8.99, "category"=>"fiction", "author"=>"Herman Melville", "title"=>"Moby Dick", "isbn"=>"0-553-21311-3"}
# ]
...and evals.
JsonPath.new('$..price[?(@ < 20)]').on(json)
# => [8.95, 8.99]
There is a convenience method, #first
that gives you the first element for a JSON object and path.
JsonPath.new('$..color').first(object)
# => "red"
As well, we can directly create an Enumerable
at any time using #[]
.
enum = JsonPath.new('$..color')[object]
# => #<JsonPath::Enumerable:...>
enum.first
# => "red"
enum.any?{ |c| c == 'red' }
# => true
You can optionally prevent eval from being called on sub-expressions by passing in :allow_eval => false to the constructor.
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"}