jsonpath-1.0.5/0000755000175000017500000000000013571230446012374 5ustar pravipravijsonpath-1.0.5/.rubocop.yml0000644000175000017500000000004013571230446014640 0ustar pravipraviinherit_from: .rubocop_todo.yml jsonpath-1.0.5/.travis.yml0000644000175000017500000000021313571230446014501 0ustar pravipravilanguage: ruby rvm: - 2.3.8 - 2.4.6 - 2.5.5 - 2.6.3 - ruby-head - jruby-head before_install: - gem install bundler -v '< 2' jsonpath-1.0.5/bin/0000755000175000017500000000000013571230446013144 5ustar pravipravijsonpath-1.0.5/bin/jsonpath0000755000175000017500000000110313571230446014713 0ustar pravipravi#!/usr/bin/env ruby # frozen_string_literal: true require 'jsonpath' require 'multi_json' def usage puts "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." exit! end usage unless ARGV[0] jsonpath = JsonPath.new(ARGV[0]) case ARGV[1] when nil # stdin puts MultiJson.encode(jsonpath.on(MultiJson.decode(STDIN.read))) when String puts MultiJson.encode(jsonpath.on(MultiJson.decode(File.exist?(ARGV[1]) ? File.read(ARGV[1]) : ARGV[1]))) end jsonpath-1.0.5/.gemtest0000644000175000017500000000000013571230446014033 0ustar pravipravijsonpath-1.0.5/LICENSE.md0000644000175000017500000000211113571230446013773 0ustar pravipraviThe MIT License (MIT) Copyright (c) 2017 Joshua Lin & Gergely Brautigam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jsonpath-1.0.5/Rakefile0000644000175000017500000000057013571230446014043 0ustar pravipravi# frozen_string_literal: true desc 'run rubocop' task(:rubocop) do require 'rubocop' cli = RuboCop::CLI.new cli.run end require 'simplecov' SimpleCov.start do add_filter '/test/' end require 'bundler' Bundler::GemHelper.install_tasks task :test do $LOAD_PATH << 'lib' Dir['./test/**/test_*.rb'].each { |test| require test } end task default: %i[test rubocop] jsonpath-1.0.5/.rubocop_todo.yml0000644000175000017500000000600513571230446015674 0ustar pravipravi# This configuration was generated by # `rubocop --auto-gen-config` # on 2019-01-25 09:23:04 +0100 using RuboCop version 0.63.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 15 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: - 'lib/jsonpath.rb' - 'lib/jsonpath/parser.rb' # Offense count: 1 Lint/IneffectiveAccessModifier: Exclude: - 'lib/jsonpath.rb' # Offense count: 17 Metrics/AbcSize: Max: 60 # Offense count: 2 # Configuration parameters: CountComments, ExcludedMethods. # ExcludedMethods: refine Metrics/BlockLength: Max: 37 # Offense count: 1 # Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 4 # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 739 # Offense count: 7 Metrics/CyclomaticComplexity: Max: 20 # Offense count: 26 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 52 # Offense count: 1 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: Max: 6 # Offense count: 6 Metrics/PerceivedComplexity: Max: 21 # Offense count: 1 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: io, id, to, by, on, in, at, ip, db Naming/UncommunicativeMethodParamName: Exclude: - 'lib/jsonpath/parser.rb' # Offense count: 15 # Configuration parameters: AllowedChars. Style/AsciiComments: Exclude: - 'lib/jsonpath/parser.rb' # Offense count: 2 Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'lib/jsonpath/enumerable.rb' - 'lib/jsonpath/proxy.rb' # Offense count: 3 # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: - 'lib/jsonpath/enumerable.rb' - 'lib/jsonpath/parser.rb' # Offense count: 2 # Cop supports --auto-correct. Style/IfUnlessModifier: Exclude: - 'lib/jsonpath/enumerable.rb' # Offense count: 1 Style/MultipleComparison: Exclude: - 'lib/jsonpath/parser.rb' # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/jsonpath/enumerable.rb' - 'lib/jsonpath/parser.rb' # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Exclude: - 'lib/jsonpath/parser.rb' # Offense count: 4 # Cop supports --auto-correct. Style/RescueModifier: Exclude: - 'lib/jsonpath/enumerable.rb' - 'lib/jsonpath/parser.rb' # Offense count: 89 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 296 jsonpath-1.0.5/Gemfile0000644000175000017500000000023513571230446013667 0ustar pravipravi# frozen_string_literal: true source 'http://rubygems.org' gemspec gem 'rubocop', require: true, group: :test gem 'simplecov', require: false, group: :test jsonpath-1.0.5/.gitignore0000644000175000017500000000007413571230446014365 0ustar pravipravipkg/* Gemfile.lock coverage/* doc/* .yardoc .DS_Store .idea jsonpath-1.0.5/.rspec0000644000175000017500000000004113571230446013504 0ustar pravipravi--colour --format doc --backtracejsonpath-1.0.5/jsonpath.gemspec0000644000175000017500000000251513571230446015572 0ustar pravipravi# frozen_string_literal: true require File.join(File.dirname(__FILE__), 'lib', 'jsonpath', 'version') Gem::Specification.new do |s| s.name = 'jsonpath' s.version = JsonPath::VERSION if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new('>= 0') end s.authors = ['Joshua Hull', 'Gergely Brautigam'] s.summary = 'Ruby implementation of http://goessner.net/articles/JsonPath/' s.description = 'Ruby implementation of http://goessner.net/articles/JsonPath/.' s.email = ['joshbuddy@gmail.com', 'skarlso777@gmail.com'] s.extra_rdoc_files = ['README.md'] s.files = `git ls-files`.split("\n") s.homepage = 'https://github.com/joshbuddy/jsonpath' s.rdoc_options = ['--charset=UTF-8'] s.require_paths = ['lib'] s.rubygems_version = '1.3.7' s.test_files = `git ls-files`.split("\n").select { |f| f =~ /^spec/ } s.rubyforge_project = 'jsonpath' s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } s.licenses = ['MIT'] # dependencies s.add_runtime_dependency 'multi_json' s.add_runtime_dependency 'to_regexp', '~> 0.2.1' s.add_development_dependency 'bundler' s.add_development_dependency 'code_stats' s.add_development_dependency 'minitest', '~> 2.2.0' s.add_development_dependency 'phocus' s.add_development_dependency 'rake' end jsonpath-1.0.5/lib/0000755000175000017500000000000013571230446013142 5ustar pravipravijsonpath-1.0.5/lib/jsonpath/0000755000175000017500000000000013571230446014770 5ustar pravipravijsonpath-1.0.5/lib/jsonpath/parser.rb0000644000175000017500000001422313571230446016613 0ustar pravipravi# frozen_string_literal: true require 'strscan' require 'to_regexp' class JsonPath # Parser parses and evaluates an expression passed to @_current_node. class Parser def initialize(node) @_current_node = node @_expr_map = {} end # parse will parse an expression in the following way. # Split the expression up into an array of legs for && and || operators. # Parse this array into a map for which the keys are the parsed legs #  of the split. This map is then used to replace the expression with their # corresponding boolean or numeric value. This might look something like this: # ((false || false) && (false || true)) #  Once this string is assembled... we proceed to evaluate from left to right. #  The above string is broken down like this: # (false && (false || true)) # (false && true) #  false def parse(exp) exps = exp.split(/(&&)|(\|\|)/) construct_expression_map(exps) @_expr_map.each { |k, v| exp.sub!(k, v.to_s) } raise ArgumentError, "unmatched parenthesis in expression: #{exp}" unless check_parenthesis_count(exp) exp = parse_parentheses(exp) while exp.include?('(') bool_or_exp(exp) end # Construct a map for which the keys are the expressions #  and the values are the corresponding parsed results. # Exp.: # {"(@['author'] =~ /herman|lukyanenko/i)"=>0} # {"@['isTrue']"=>true} def construct_expression_map(exps) exps.each_with_index do |item, _index| next if item == '&&' || item == '||' item = item.strip.gsub(/\)*$/, '').gsub(/^\(*/, '') @_expr_map[item] = parse_exp(item) end end # Using a scanner break down the individual expressions and determine if # there is a match in the JSON for it or not. def parse_exp(exp) exp = exp.sub(/@/, '').gsub(/^\(/, '').gsub(/\)$/, '').tr('"', '\'').strip exp.scan(/^\[(\d+)\]/) do |i| next if i.empty? index = Integer(i[0]) raise ArgumentError, 'Node does not appear to be an array.' unless @_current_node.is_a?(Array) raise ArgumentError, "Index out of bounds for nested array. Index: #{index}" if @_current_node.size < index @_current_node = @_current_node[index] # Remove the extra '' and the index. exp = exp.gsub(/^\[\d+\]|\[''\]/, '') end scanner = StringScanner.new(exp) elements = [] until scanner.eos? if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?!]?/)) elements << t.gsub(/[\[\]'.]|\s+/, '') elsif (t = scanner.scan(/(\s+)?[<>=!\-+][=~]?(\s+)?/)) operator = t elsif (t = scanner.scan(/(\s+)?'?.*'?(\s+)?/)) # If we encounter a node which does not contain `'` it means #  that we are dealing with a boolean type. operand = if t == 'true' true elsif t == 'false' false else operator.to_s.strip == '=~' ? t.to_regexp : t.gsub(%r{^'|'$}, '').strip end elsif (t = scanner.scan(/\/\w+\//)) elsif (t = scanner.scan(/.*/)) raise "Could not process symbol: #{t}" end end el = if elements.empty? @_current_node elsif @_current_node.is_a?(Hash) @_current_node.dig(*elements) else elements.inject(@_current_node, &:__send__) end return (el ? true : false) if el.nil? || operator.nil? el = Float(el) rescue el operand = Float(operand) rescue operand el.__send__(operator.strip, operand) end private #  This will break down a parenthesis from the left to the right #  and replace the given expression with it's returned value. # It does this in order to make it easy to eliminate groups # one-by-one. def parse_parentheses(str) opening_index = 0 closing_index = 0 (0..str.length - 1).step(1) do |i| opening_index = i if str[i] == '(' if str[i] == ')' closing_index = i break end end to_parse = str[opening_index + 1..closing_index - 1] #  handle cases like (true && true || false && true) in # one giant parenthesis. top = to_parse.split(/(&&)|(\|\|)/) top = top.map(&:strip) res = bool_or_exp(top.shift) top.each_with_index do |item, index| if item == '&&' res &&= top[index + 1] elsif item == '||' res ||= top[index + 1] end end #  if we are at the last item, the opening index will be 0 # and the closing index will be the last index. To avoid # off-by-one errors we simply return the result at that point. if closing_index + 1 >= str.length && opening_index == 0 res.to_s else "#{str[0..opening_index - 1]}#{res}#{str[closing_index + 1..str.length]}" end end #  This is convoluted and I should probably refactor it somehow. #  The map that is created will contain strings since essentially I'm # constructing a string like `true || true && false`. # With eval the need for this would disappear but never the less, here #  it is. The fact is that the results can be either boolean, or a number # in case there is only indexing happening like give me the 3rd item... or # it also can be nil in case of regexes or things that aren't found. # Hence, I have to be clever here to see what kind of variable I need to # provide back. def bool_or_exp(b) if b.to_s == 'true' return true elsif b.to_s == 'false' return false elsif b.to_s == '' return nil end b = Float(b) rescue b b end # this simply makes sure that we aren't getting into the whole #  parenthesis parsing business without knowing that every parenthesis # has its pair. def check_parenthesis_count(exp) return true unless exp.include?('(') depth = 0 exp.chars.each do |c| if c == '(' depth += 1 elsif c == ')' depth -= 1 end end depth == 0 end end end jsonpath-1.0.5/lib/jsonpath/proxy.rb0000644000175000017500000000257413571230446016506 0ustar pravipravi# frozen_string_literal: true class JsonPath class Proxy attr_reader :obj alias to_hash obj def initialize(obj) @obj = obj end def gsub(path, replacement = nil, &replacement_block) _gsub(_deep_copy, path, replacement ? proc(&method(:replacement)) : replacement_block) end def gsub!(path, replacement = nil, &replacement_block) _gsub(@obj, path, replacement ? proc(&method(:replacement)) : replacement_block) end def delete(path = JsonPath::PATH_ALL) _delete(_deep_copy, path) end def delete!(path = JsonPath::PATH_ALL) _delete(@obj, path) end def compact(path = JsonPath::PATH_ALL) _compact(_deep_copy, path) end def compact!(path = JsonPath::PATH_ALL) _compact(@obj, path) end private def _deep_copy Marshal.load(Marshal.dump(@obj)) end def _gsub(obj, path, replacement) JsonPath.new(path)[obj, :substitute].each(&replacement) Proxy.new(obj) end def _delete(obj, path) JsonPath.new(path)[obj, :delete].each obj = _remove(obj) Proxy.new(obj) end def _remove(obj) obj.each do |o| if o.is_a?(Hash) || o.is_a?(Array) _remove(o) o.delete({}) end end end def _compact(obj, path) JsonPath.new(path)[obj, :compact].each Proxy.new(obj) end end end jsonpath-1.0.5/lib/jsonpath/enumerable.rb0000644000175000017500000001256513571230446017445 0ustar pravipravi# frozen_string_literal: true class JsonPath class Enumerable include ::Enumerable def initialize(path, object, mode, options = {}) @path = path.path @object = object @mode = mode @options = options end def each(context = @object, key = nil, pos = 0, &blk) node = if key context.is_a?(Hash) || context.is_a?(Array) ? context[key] : context.__send__(key) else context end @_current_node = node return yield_value(blk, context, key) if pos == @path.size case expr = @path[pos] when '*', '..', '@' each(context, key, pos + 1, &blk) when '$' each(context, key, pos + 1, &blk) if node == @object when /^\[(.*)\]$/ handle_wildecard(node, expr, context, key, pos, &blk) when /\(.*\)/ keys = expr.gsub(/[()]/, '').split(',').map(&:strip) new_context = filter_context(context, keys) yield_value(blk, new_context, key) end if pos > 0 && @path[pos - 1] == '..' || (@path[pos - 1] == '*' && @path[pos] != '..') case node when Hash then node.each { |k, _| each(node, k, pos, &blk) } when Array then node.each_with_index { |_, i| each(node, i, pos, &blk) } end end end private def filter_context(context, keys) case context when Hash # TODO: Change this to `slice(*keys)` when ruby version support is > 2.4 context.select { |k| keys.include?(k) } when Array context.each_with_object([]) do |c, memo| memo << c.select { |k| keys.include?(k) } end end end def handle_wildecard(node, expr, _context, _key, pos, &blk) expr[1, expr.size - 2].split(',').each do |sub_path| case sub_path[0] when '\'', '"' k = sub_path[1, sub_path.size - 2] if node.is_a?(Hash) node[k] ||= nil if @options[:default_path_leaf_to_null] each(node, k, pos + 1, &blk) if node.key?(k) elsif node.respond_to?(k.to_s) && !Object.respond_to?(k.to_s) each(node, k, pos + 1, &blk) end when '?' handle_question_mark(sub_path, node, pos, &blk) else next if node.is_a?(Array) && node.empty? array_args = sub_path.split(':') if array_args[0] == '*' start_idx = 0 end_idx = node.size - 1 elsif sub_path.count(':') == 0 start_idx = end_idx = process_function_or_literal(array_args[0], 0) next unless start_idx next if start_idx >= node.size else start_idx = process_function_or_literal(array_args[0], 0) next unless start_idx end_idx = array_args[1] && ensure_exclusive_end_index(process_function_or_literal(array_args[1], -1)) || -1 next unless end_idx next if start_idx == end_idx && start_idx >= node.size end start_idx %= node.size end_idx %= node.size step = process_function_or_literal(array_args[2], 1) next unless step if @mode == :delete (start_idx..end_idx).step(step) { |i| node[i] = nil } node.compact! else (start_idx..end_idx).step(step) { |i| each(node, i, pos + 1, &blk) } end end end end def ensure_exclusive_end_index(value) return value unless value.is_a?(Integer) && value > 0 value - 1 end def handle_question_mark(sub_path, node, pos, &blk) case node when Array node.size.times do |index| @_current_node = node[index] if process_function_or_literal(sub_path[1, sub_path.size - 1]) each(@_current_node, nil, pos + 1, &blk) end end when Hash if process_function_or_literal(sub_path[1, sub_path.size - 1]) each(@_current_node, nil, pos + 1, &blk) end else yield node if process_function_or_literal(sub_path[1, sub_path.size - 1]) end end def yield_value(blk, context, key) key = Integer(key) rescue key if key case @mode when nil blk.call(key ? context[key] : context) when :compact if key && context[key].nil? key.is_a?(Integer) ? context.delete_at(key) : context.delete(key) end when :delete if key key.is_a?(Integer) ? context.delete_at(key) : context.delete(key) else context.replace({}) end when :substitute if key context[key] = blk.call(context[key]) else context.replace(blk.call(context[key])) end end end def process_function_or_literal(exp, default = nil) return default if exp.nil? || exp.empty? return Integer(exp) if exp[0] != '(' return nil unless @_current_node identifiers = /@?((?<=] \d+/)) @path.last << token elsif (token = scanner.scan(/./)) begin @path.last << token rescue RuntimeError raise ArgumentError, "character '#{token}' not supported in query" end end end end def find_matching_brackets(token, scanner) count = 1 until count.zero? if (t = scanner.scan(/\[/)) token << t count += 1 elsif (t = scanner.scan(/\]/)) token << t count -= 1 elsif (t = scanner.scan(/[^\[\]]+/)) token << t elsif scanner.eos? raise ArgumentError, 'unclosed bracket' end end token end def join(join_path) res = deep_clone res.path += JsonPath.new(join_path).path res end def on(obj_or_str, opts = {}) a = enum_on(obj_or_str).to_a if opts[:symbolize_keys] a.map! do |e| e.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v; } end end a end def first(obj_or_str, *args) enum_on(obj_or_str).first(*args) end def enum_on(obj_or_str, mode = nil) JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode, @opts) end alias [] enum_on def self.on(obj_or_str, path, opts = {}) new(path, opts).on(process_object(obj_or_str)) end def self.for(obj_or_str) Proxy.new(process_object(obj_or_str)) end private def self.process_object(obj_or_str) obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str end def deep_clone Marshal.load Marshal.dump(self) end end jsonpath-1.0.5/README.md0000644000175000017500000001210213571230446013647 0ustar pravipravi# JsonPath This is an implementation of http://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'! ```ruby 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. ```ruby path = JsonPath.new('$..price') ``` Now that we have a path, let's apply it to the object above. ```ruby path.on(json) # => [19.95, 8.95, 12.99, 8.99, 22.99] ``` Or on some other object ... ```ruby 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. ```ruby JsonPath.on(json, '$..author') # => ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "Tolkien"] ``` Of course the full JsonPath syntax is supported, such as array slices ```ruby 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. ```ruby JsonPath.new('$..price[?(@ < 10)]').on(json) # => [8.95, 8.99] ``` There is a convenience method, `#first` that gives you the first element for a JSON object and path. ```ruby JsonPath.new('$..color').first(object) # => "red" ``` As well, we can directly create an `Enumerable` at any time using `#[]`. ```ruby enum = JsonPath.new('$..color')[object] # => # enum.first # => "red" enum.any?{ |c| c == 'red' } # => true ``` ### More examples For more usage examples and variations on paths, please visit the tests. There are some more complex ones as well. ### Conditional Operators Are Also Supported ```ruby def test_or_operator assert_equal [@object['store']['book'][1], @object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] == 13 || @['price'] == 23)]").on(@object) end def test_and_operator assert_equal [], JsonPath.new("$..book[?(@['price'] == 13 && @['price'] == 23)]").on(@object) end def test_and_operator_with_more_results assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object) end ``` ### Selecting Values It's possible to select results once a query has been defined after the query. For example given this JSON data: ```bash { "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: ```ruby "$.store.book[*](category,author)" ``` ... the result can be filtered as such: ```bash [ { "category" : "reference", "author" : "Nigel Rees" }, { "category" : "fiction", "author" : "Evelyn Waugh" } ] ``` ### Running an individual test ```ruby ruby -Ilib:../lib test/test_jsonpath.rb --name test_wildcard_on_intermediary_element_v6 ``` ### Manipulation If you'd like to do substitution in a json object, you can use `#gsub` or `#gsub!` to modify the object in place. ```ruby JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash ``` The result will be ```ruby {'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: ```ruby json = '{"candy":"lollipop","noncandy":null,"other":"things"}' o = JsonPath.for(json). gsub('$..candy') {|v| "big turks" }. compact. delete('$..other'). to_hash # => {"candy" => "big turks"} ``` # Contributions Please feel free to submit an Issue or a Pull Request any time you feel like you would like to contribute. Thank you! jsonpath-1.0.5/test/0000755000175000017500000000000013571230446013353 5ustar pravipravijsonpath-1.0.5/test/test_jsonpath_bin.rb0000644000175000017500000000102113571230446017407 0ustar pravipravi# frozen_string_literal: true require 'minitest/autorun' require 'phocus' require 'jsonpath' class TestJsonpathBin < MiniTest::Unit::TestCase def setup @runner = 'ruby -Ilib bin/jsonpath' @original_dir = Dir.pwd Dir.chdir(File.join(File.dirname(__FILE__), '..')) end def teardown Dir.chdir(@original_dir) `rm /tmp/test.json` end def test_stdin File.open('/tmp/test.json', 'w') { |f| f << '{"test": "time"}' } assert_equal '["time"]', `#{@runner} '$.test' /tmp/test.json`.strip end end jsonpath-1.0.5/test/test_jsonpath.rb0000644000175000017500000007236613571230446016603 0ustar pravipravi# frozen_string_literal: true require 'minitest/autorun' require 'phocus' require 'jsonpath' require 'json' class TestJsonpath < MiniTest::Unit::TestCase def setup @object = example_object @object2 = example_object end def test_bracket_matching assert_raises(ArgumentError) { JsonPath.new('$.store.book[0') } assert_raises(ArgumentError) { JsonPath.new('$.store.book[0]]') } assert_equal [9], JsonPath.new('$.store.book[0].price').on(@object) end def test_lookup_direct_path assert_equal 7, JsonPath.new('$.store.*').on(@object).first['book'].size end def test_lookup_missing_element assert_equal [], JsonPath.new('$.store.book[99].price').on(@object) end def test_retrieve_all_authors assert_equal [ @object['store']['book'][0]['author'], @object['store']['book'][1]['author'], @object['store']['book'][2]['author'], @object['store']['book'][3]['author'], @object['store']['book'][4]['author'], @object['store']['book'][5]['author'], @object['store']['book'][6]['author'] ], JsonPath.new('$..author').on(@object) end def test_retrieve_all_prices assert_equal [ @object['store']['bicycle']['price'], @object['store']['book'][0]['price'], @object['store']['book'][1]['price'], @object['store']['book'][2]['price'], @object['store']['book'][3]['price'] ].sort, JsonPath.new('$..price').on(@object).sort end def test_recognize_array_splices assert_equal [@object['store']['book'][0]], JsonPath.new('$..book[0:1:1]').on(@object) assert_equal [@object['store']['book'][0], @object['store']['book'][1]], JsonPath.new('$..book[0:2:1]').on(@object) assert_equal [@object['store']['book'][1], @object['store']['book'][3], @object['store']['book'][5]], JsonPath.new('$..book[1::2]').on(@object) assert_equal [@object['store']['book'][0], @object['store']['book'][2], @object['store']['book'][4], @object['store']['book'][6]], JsonPath.new('$..book[::2]').on(@object) assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new('$..book[:-5:2]').on(@object) assert_equal [@object['store']['book'][5], @object['store']['book'][6]], JsonPath.new('$..book[5::]').on(@object) end def test_slice_array_with_exclusive_end_correctly assert_equal [@object['store']['book'][0], @object['store']['book'][1]], JsonPath.new('$..book[:2]').on(@object) end def test_recognize_array_comma assert_equal [@object['store']['book'][0], @object['store']['book'][1]], JsonPath.new('$..book[0,1]').on(@object) assert_equal [@object['store']['book'][2], @object['store']['book'][6]], JsonPath.new('$..book[2,-1::]').on(@object) end def test_recognize_filters assert_equal [@object['store']['book'][2], @object['store']['book'][3]], JsonPath.new("$..book[?(@['isbn'])]").on(@object) assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] < 10)]").on(@object) assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] == 9)]").on(@object) assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] > 20)]").on(@object) assert_equal [ @object['store']['book'][0], @object['store']['book'][4], @object['store']['book'][5], @object['store']['book'][6] ], JsonPath.new("$..book[?(@['category'] != 'fiction')]").on(@object) end def test_or_operator assert_equal [@object['store']['book'][1], @object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] == 13 || @['price'] == 23)]").on(@object) end def test_and_operator assert_equal [], JsonPath.new("$..book[?(@['price'] == 13 && @['price'] == 23)]").on(@object) end def test_and_operator_with_more_results assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object) end def test_nested_grouping path = "$..book[?((@['price'] == 19 && @['author'] == 'Herman Melville') || @['price'] == 23)]" assert_equal [@object['store']['book'][3]], JsonPath.new(path).on(@object) end def test_eval_with_floating_point_and_and assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] < 23.0 && @['price'] > 9.0)]").on(@object) end def test_eval_with_floating_point assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] == 13.0)]").on(@object) end def test_paths_with_underscores assert_equal [@object['store']['bicycle']['catalogue_number']], JsonPath.new('$.store.bicycle.catalogue_number').on(@object) end def test_path_with_hyphens assert_equal [@object['store']['bicycle']['single-speed']], JsonPath.new('$.store.bicycle.single-speed').on(@object) end def test_path_with_colon assert_equal [@object['store']['bicycle']['make:model']], JsonPath.new('$.store.bicycle.make:model').on(@object) end def test_paths_with_numbers assert_equal [@object['store']['bicycle']['2seater']], JsonPath.new('$.store.bicycle.2seater').on(@object) end def test_recognized_dot_notation_in_filters assert_equal [@object['store']['book'][2], @object['store']['book'][3]], JsonPath.new('$..book[?(@.isbn)]').on(@object) end def test_works_on_non_hash klass = Struct.new(:a, :b) object = klass.new('some', 'value') assert_equal ['value'], JsonPath.new('$.b').on(object) end def test_works_on_non_hash_with_filters klass = Struct.new(:a, :b) first_object = klass.new('some', 'value') second_object = klass.new('next', 'other value') assert_equal ['other value'], JsonPath.new('$[?(@.a == "next")].b').on([first_object, second_object]) end def test_recognize_array_with_evald_index assert_equal [@object['store']['book'][2]], JsonPath.new('$..book[(@.length-5)]').on(@object) end def test_use_first assert_equal @object['store']['book'][2], JsonPath.new('$..book[(@.length-5)]').first(@object) end def test_counting assert_equal 57, JsonPath.new('$..*').on(@object).to_a.size end def test_space_in_path assert_equal ['e'], JsonPath.new("$.'c d'").on('a' => 'a', 'b' => 'b', 'c d' => 'e') end def test_class_method assert_equal JsonPath.new('$..author').on(@object), JsonPath.on(@object, '$..author') end def test_join assert_equal JsonPath.new('$.store.book..author').on(@object), JsonPath.new('$.store').join('book..author').on(@object) end def test_gsub @object2['store']['bicycle']['price'] += 10 @object2['store']['book'][0]['price'] += 10 @object2['store']['book'][1]['price'] += 10 @object2['store']['book'][2]['price'] += 10 @object2['store']['book'][3]['price'] += 10 assert_equal @object2, JsonPath.for(@object).gsub('$..price') { |p| p + 10 }.to_hash end def test_gsub! JsonPath.for(@object).gsub!('$..price') { |p| p + 10 } assert_equal 30, @object['store']['bicycle']['price'] assert_equal 19, @object['store']['book'][0]['price'] assert_equal 23, @object['store']['book'][1]['price'] assert_equal 19, @object['store']['book'][2]['price'] assert_equal 33, @object['store']['book'][3]['price'] end def test_weird_gsub! h = { 'hi' => 'there' } JsonPath.for(@object).gsub!('$.*') { |_| h } assert_equal h, @object end def test_gsub_to_false! h = { 'hi' => 'there' } h2 = { 'hi' => false } assert_equal h2, JsonPath.for(h).gsub!('$.hi') { |_| false }.to_hash end def test_where_selector JsonPath.for(@object).gsub!('$..book.price[?(@ > 20)]') { |p| p + 10 } end def test_compact h = { 'hi' => 'there', 'you' => nil } JsonPath.for(h).compact! assert_equal({ 'hi' => 'there' }, h) end def test_delete h = { 'hi' => 'there', 'you' => nil } JsonPath.for(h).delete!('*.hi') assert_equal({ 'you' => nil }, h) end def test_delete_2 json = { 'store' => { 'book' => [ { 'category' => 'reference', 'author' => 'Nigel Rees', 'title' => 'Sayings of the Century', 'price' => 9, 'tags' => %w[asdf asdf2] }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13 }, { 'category' => 'fiction', 'author' => 'Aasdf', 'title' => 'Aaasdf2', 'price' => 1 } ] } } json_deleted = { 'store' => { 'book' => [ { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13 }, { 'category' => 'fiction', 'author' => 'Aasdf', 'title' => 'Aaasdf2', 'price' => 1 } ] } } assert_equal(json_deleted, JsonPath.for(json).delete("$..store.book[?(@.category == 'reference')]").obj) end def test_delete_3 json = { 'store' => { 'book' => [ { 'category' => 'reference', 'author' => 'Nigel Rees', 'title' => 'Sayings of the Century', 'price' => 9, 'tags' => %w[asdf asdf2], 'this' => { 'delete_me' => [ 'no' => 'do not' ] } }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13 }, { 'category' => 'fiction', 'author' => 'Aasdf', 'title' => 'Aaasdf2', 'price' => 1 } ] } } json_deleted = { 'store' => { 'book' => [ { 'category' => 'reference', 'author' => 'Nigel Rees', 'title' => 'Sayings of the Century', 'price' => 9, 'tags' => %w[asdf asdf2], 'this' => {} }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13 }, { 'category' => 'fiction', 'author' => 'Aasdf', 'title' => 'Aaasdf2', 'price' => 1 } ] } } assert_equal(json_deleted, JsonPath.for(json).delete('$..store.book..delete_me').obj) end def test_delete_for_array before = JsonPath.on(@object, '$..store.book[1]') JsonPath.for(@object).delete!('$..store.book[0]') after = JsonPath.on(@object, '$..store.book[0]') assert_equal(after, before, 'Before is the second element. After should have been equal to the next element after delete.') end def test_at_sign_in_json_element data = { '@colors' => [{ '@r' => 255, '@g' => 0, '@b' => 0 }, { '@r' => 0, '@g' => 255, '@b' => 0 }, { '@r' => 0, '@g' => 0, '@b' => 255 }] } assert_equal [255, 0, 0], JsonPath.on(data, '$..@r') end def test_wildcard assert_equal @object['store']['book'].collect { |e| e['price'] }.compact, JsonPath.on(@object, '$..book[*].price') end def test_wildcard_on_intermediary_element assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'c' => 1 } } }, '$.a..c') end def test_wildcard_on_intermediary_element_v2 assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a..c') end def test_wildcard_on_intermediary_element_v3 assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a.*..c') end def test_wildcard_on_intermediary_element_v4 assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a.*..c') end def test_wildcard_on_intermediary_element_v5 assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'c' => 1 } } }, '$.a.*.c') end def test_wildcard_on_intermediary_element_v6 assert_equal ['red'], JsonPath.new('$.store.*.color').on(@object) end def test_wildcard_empty_array object = @object.merge('bicycle' => { 'tire' => [] }) assert_equal [], JsonPath.on(object, '$..bicycle.tire[*]') end def test_support_filter_by_array_childnode_value assert_equal [@object['store']['book'][3]], JsonPath.new('$..book[?(@.price > 20)]').on(@object) end def test_support_filter_by_childnode_value_with_inconsistent_children @object['store']['book'][0] = 'string_instead_of_object' assert_equal [@object['store']['book'][3]], JsonPath.new('$..book[?(@.price > 20)]').on(@object) end def test_support_filter_by_childnode_value_and_select_child_key assert_equal [23], JsonPath.new('$..book[?(@.price > 20)].price').on(@object) end def test_support_filter_by_childnode_value_over_childnode_and_select_child_key assert_equal ['Osennie Vizity'], JsonPath.new('$..book[?(@.written.year == 1996)].title').on(@object) end def test_support_filter_by_object_childnode_value data = { 'data' => { 'type' => 'users', 'id' => '123' } } assert_equal [{ 'type' => 'users', 'id' => '123' }], JsonPath.new("$.data[?(@.type == 'users')]").on(data) assert_equal [], JsonPath.new("$.[?(@.type == 'admins')]").on(data) end def test_support_at_sign_in_member_names assert_equal [@object['store']['@id']], JsonPath.new('$.store.@id').on(@object) end def test_support_dollar_sign_in_member_names assert_equal [@object['store']['$meta-data']], JsonPath.new('$.store.$meta-data').on(@object) end def test_support_underscore_in_member_names assert_equal [@object['store']['_links']], JsonPath.new('$.store._links').on(@object) end def test_dig_return_string assert_equal ['asdf'], JsonPath.new("$.store.book..tags[?(@ == 'asdf')]").on(@object) assert_equal [], JsonPath.new("$.store.book..tags[?(@ == 'not_asdf')]").on(@object) end def test_slash_in_value data = { 'data' => [{ 'type' => 'mps/awesome' }, { 'type' => 'not' }] } assert_equal [{ 'type' => 'mps/awesome' }], JsonPath.new('$.data[?(@.type == "mps/awesome")]').on(data) end def test_floating_point_with_precision_marker data = { 'data' => { 'type' => 0.00001 } } assert_equal [{ 'type' => 0.00001 }], JsonPath.new('$.data[?(@.type == 0.00001)]').on(data) end def test_digits_only_string data = { 'foo' => { 'type' => 'users', 'id' => '123' } } assert_equal([{ 'type' => 'users', 'id' => '123' }], JsonPath.new("$.foo[?(@.id == '123')]").on(data)) end def test_digits_only_string_in_array data = { 'foo' => [{ 'type' => 'users', 'id' => '123' }, { 'type' => 'users', 'id' => '321' }] } assert_equal([{ 'type' => 'users', 'id' => '123' }], JsonPath.new("$.foo[?(@.id == '123')]").on(data)) end def test_at_in_filter jsonld = { 'mentions' => [ { 'name' => 'Delimara Powerplant', 'identifier' => 'krzana://took/powerstation/Delimara Powerplant', '@type' => 'Place', 'geo' => { 'latitude' => 35.83020073454, 'longitude' => 14.55602645874 } } ] } assert_equal(['Place'], JsonPath.new("$..mentions[?(@['@type'] == 'Place')].@type").on(jsonld)) end def test_dollar_in_filter jsonld = { 'mentions' => [ { 'name' => 'Delimara Powerplant', 'identifier' => 'krzana://took/powerstation/Delimara Powerplant', '$type' => 'Place', 'geo' => { 'latitude' => 35.83020073454, 'longitude' => 14.55602645874 } } ] } assert_equal(['Place'], JsonPath.new("$..mentions[?(@['$type'] == 'Place')].$type").on(jsonld)) end def test_underscore_in_filter jsonld = { 'attributes' => [ { 'store' => [ { 'with' => 'urn' }, { 'with_underscore' => 'urn:1' } ] } ] } assert_equal(['urn:1'], JsonPath.new("$.attributes..store[?(@['with_underscore'] == 'urn:1')].with_underscore").on(jsonld)) end def test_at_in_value jsonld = { 'mentions' => { 'name' => 'Delimara Powerplant', 'identifier' => 'krzana://took/powerstation/Delimara Powerplant', 'type' => '@Place', 'geo' => { 'latitude' => 35.83020073454, 'longitude' => 14.55602645874 } } } assert_equal(['@Place'], JsonPath.new("$..mentions.type[?(@ == '@Place')]").on(jsonld)) end def test_parens_in_value data = { 'data' => { 'number' => '(492) 080-3961' } } assert_equal [{ 'number' => '(492) 080-3961' }], JsonPath.new("$.data[?(@.number == '(492) 080-3961')]").on(data) end def test_boolean_parameter_value data = { 'data' => [{ 'isTrue' => true, 'name' => 'testname1' }, { 'isTrue' => false, 'name' => 'testname2' }] } assert_equal [{ 'isTrue' => true, 'name' => 'testname1' }], JsonPath.new('$.data[?(@.isTrue)]').on(data) end def test_regex assert_equal [], JsonPath.new('$..book[?(@.author =~ /herman/)]').on(@object) assert_equal [ @object['store']['book'][2], @object['store']['book'][4], @object['store']['book'][5], @object['store']['book'][6] ], JsonPath.new('$..book[?(@.author =~ /herman|lukyanenko/i)]').on(@object) assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@ =~ /asdf/)]').on(@object) end def test_regression_1 json = { ok: true, channels: [ { id: 'C09C5GYHF', name: 'general' }, { id: 'C09C598QL', name: 'random' } ] }.to_json assert_equal 'C09C5GYHF', JsonPath.on(json, "$..channels[?(@.name == 'general')].id")[0] end def test_regression_2 json = { ok: true, channels: [ { id: 'C09C5GYHF', name: 'general', is_archived: false }, { id: 'C09C598QL', name: 'random', is_archived: true } ] }.to_json assert_equal 'C09C5GYHF', JsonPath.on(json, '$..channels[?(@.is_archived == false)].id')[0] end def test_regression_3 json = { ok: true, channels: [ { id: 'C09C5GYHF', name: 'general', is_archived: false }, { id: 'C09C598QL', name: 'random', is_archived: true } ] }.to_json assert_equal 'C09C598QL', JsonPath.on(json, '$..channels[?(@.is_archived)].id')[0] end def test_regression_4 json = { ok: true, channels: [ { id: 'C09C5GYHF', name: 'general', is_archived: false }, { id: 'C09C598QL', name: 'random', is_archived: true } ] }.to_json assert_equal ['C09C5GYHF'], JsonPath.on(json, "$..channels[?(@.name == 'general')].id") end def test_regression_5 json = { ok: true, channels: [ { id: 'C09C5GYHF', name: 'general', is_archived: 'false' }, { id: 'C09C598QL', name: 'random', is_archived: true } ] }.to_json assert_equal 'C09C5GYHF', JsonPath.on(json, "$..channels[?(@.is_archived == 'false')].id")[0] end def test_quote json = { channels: [ { name: "King's Speech" } ] }.to_json assert_equal [{ 'name' => "King\'s Speech" }], JsonPath.on(json, "$..channels[?(@.name == 'King\'s Speech')]") end def test_curly_brackets data = { '{data}' => 'data' } assert_equal ['data'], JsonPath.new('$.{data}').on(data) end def test_symbolize data = ' { "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" } ] } } ' assert_equal [{ 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' }], JsonPath.new('$..book[::2]').on(data, symbolize_keys: true) end def test_changed json = { 'snapshot' => { 'objects' => { 'whatever' => [ { 'column' => { 'name' => 'ASSOCIATE_FLAG', 'nullable' => true } }, { 'column' => { 'name' => 'AUTHOR', 'nullable' => false } } ] } } } assert_equal true, JsonPath.on(json, "$..column[?(@.name == 'ASSOCIATE_FLAG')].nullable")[0] end def test_another json = { initial: true, not: true }.to_json assert_equal [{ 'initial' => true, 'not' => true }], JsonPath.on(json, '$.[?(@.initial == true)]') json = { initial: false, not: true }.to_json assert_equal [], JsonPath.on(json, '$.initial[?(@)]') assert_equal [], JsonPath.on(json, '$.[?(@.initial == true)]') assert_equal [{ 'initial' => false, 'not' => true }], JsonPath.on(json, '$.[?(@.initial == false)]') json = { initial: 'false', not: true }.to_json assert_equal [{ 'initial' => 'false', 'not' => true }], JsonPath.on(json, "$.[?(@.initial == 'false')]") assert_equal [], JsonPath.on(json, '$.[?(@.initial == false)]') end def test_hanging json = { initial: true }.to_json success_path = '$.initial' assert_equal [true], JsonPath.on(json, success_path) broken_path = "$.initial\n" assert_equal [true], JsonPath.on(json, broken_path) end def test_complex_nested_grouping path = "$..book[?((@['author'] == 'Evelyn Waugh' || @['author'] == 'Herman Melville') && (@['price'] == 33 || @['price'] == 9))]" assert_equal [@object['store']['book'][2]], JsonPath.new(path).on(@object) end def test_complex_nested_grouping_unmatched_parent path = "$..book[?((@['author'] == 'Evelyn Waugh' || @['author'] == 'Herman Melville' && (@['price'] == 33 || @['price'] == 9))]" err = assert_raises(ArgumentError, 'should have raised an exception') { JsonPath.new(path).on(@object) } assert_match(/unmatched parenthesis in expression: \(\(false \|\| false && \(false \|\| true\)\)/, err.message) end def test_runtime_error_frozen_string skip('in ruby version below 2.2.0 this error is not raised') if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.0') json = ' { "test": "something" } '.to_json assert_raises(ArgumentError, "RuntimeError: character '|' not supported in query") do JsonPath.on(json, '$.description|title') end end def test_delete_more_items a = { 'itemList' => [{ 'alfa' => 'beta1' }, { 'alfa' => 'beta2' }, { 'alfa' => 'beta3' }, { 'alfa' => 'beta4' }, { 'alfa' => 'beta5' }, { 'alfa' => 'beta6' }, { 'alfa' => 'beta7' }, { 'alfa' => 'beta8' }, { 'alfa' => 'beta9' }, { 'alfa' => 'beta10' }, { 'alfa' => 'beta11' }, { 'alfa' => 'beta12' }] } expected = { 'itemList' => [{ 'alfa' => 'beta1' }] } assert_equal expected, JsonPath.for(a.to_json).delete('$.itemList[1:12:1]').to_hash end def test_delete_more_items_with_stepping a = { 'itemList' => [{ 'alfa' => 'beta1' }, { 'alfa' => 'beta2' }, { 'alfa' => 'beta3' }, { 'alfa' => 'beta4' }, { 'alfa' => 'beta5' }, { 'alfa' => 'beta6' }, { 'alfa' => 'beta7' }, { 'alfa' => 'beta8' }, { 'alfa' => 'beta9' }, { 'alfa' => 'beta10' }, { 'alfa' => 'beta11' }, { 'alfa' => 'beta12' }] } expected = { 'itemList' => [{ 'alfa' => 'beta1' }, { 'alfa' => 'beta3' }, { 'alfa' => 'beta5' }, { 'alfa' => 'beta7' }, { 'alfa' => 'beta8' }, { 'alfa' => 'beta9' }, { 'alfa' => 'beta10' }, { 'alfa' => 'beta11' }, { 'alfa' => 'beta12' }] } assert_equal expected, JsonPath.for(a.to_json).delete('$.itemList[1:6:2]').to_hash end def test_nested_values json = ' { "phoneNumbers": [ [{ "type" : "iPhone", "number": "0123-4567-8888" }], [{ "type" : "home", "number": "0123-4567-8910" }] ] } '.to_json assert_equal [[{ 'type' => 'home', 'number' => '0123-4567-8910' }]], JsonPath.on(json, "$.phoneNumbers[?(@[0].type == 'home')]") assert_equal [], JsonPath.on(json, "$.phoneNumbers[?(@[2].type == 'home')]") json = ' { "phoneNumbers": { "type" : "iPhone", "number": "0123-4567-8888" } } '.to_json assert_equal [], JsonPath.on(json, "$.phoneNumbers[?(@[0].type == 'home')]") end def test_selecting_multiple_keys json = ' { "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 } ] } } '.to_json assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh' }], JsonPath.on(json, '$.store.book[*](category,author)') end def test_selecting_multiple_keys_with_filter json = ' { "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 } ] } } '.to_json assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, "$.store.book[?(@['price'] == 8.95)](category,author)") assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, "$.store.book[?(@['price'] == 8.95)]( category, author )") end def test_selecting_multiple_keys_with_filter_with_space_in_catergory json = ' { "store": { "book": [ { "cate gory": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "cate gory": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 } ] } } '.to_json assert_equal [{ 'cate gory' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, "$.store.book[?(@['price'] == 8.95)]( cate gory, author )") end def test_object_method_send j = {height: 5, hash: "some_hash"}.to_json hs = JsonPath.new "$..send" assert_equal([], hs.on(j)) hs = JsonPath.new "$..hash" assert_equal(["some_hash"], hs.on(j)) hs = JsonPath.new "$..send" assert_equal([], hs.on(j)) j = {height: 5, send: "should_still_work"}.to_json hs = JsonPath.new "$..send" assert_equal(['should_still_work'], hs.on(j)) end def example_object { 'store' => { 'book' => [ { 'category' => 'reference', 'author' => 'Nigel Rees', 'title' => 'Sayings of the Century', 'price' => 9, 'tags' => %w[asdf asdf2] }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13 }, { 'category' => 'fiction', 'author' => 'Herman Melville', 'title' => 'Moby Dick', 'isbn' => '0-553-21311-3', 'price' => 9 }, { 'category' => 'fiction', 'author' => 'J. R. R. Tolkien', 'title' => 'The Lord of the Rings', 'isbn' => '0-395-19395-8', 'price' => 23 }, { 'category' => 'russian_fiction', 'author' => 'Lukyanenko', 'title' => 'Imperatory Illuziy', 'written' => { 'year' => 1995 } }, { 'category' => 'russian_fiction', 'author' => 'Lukyanenko', 'title' => 'Osennie Vizity', 'written' => { 'year' => 1996 } }, { 'category' => 'russian_fiction', 'author' => 'Lukyanenko', 'title' => 'Ne vremya dlya drakonov', 'written' => { 'year' => 1997 } } ], 'bicycle' => { 'color' => 'red', 'price' => 20, 'catalogue_number' => 123_45, 'single-speed' => 'no', '2seater' => 'yes', 'make:model' => 'Zippy Sweetwheeler' }, '@id' => 'http://example.org/store/42', '$meta-data' => 'whatevs', '_links' => { 'self' => {} } } } end end