jsonpath-1.1.5/0000755000004100000410000000000014567454057013415 5ustar www-datawww-datajsonpath-1.1.5/bin/0000755000004100000410000000000014567454057014165 5ustar www-datawww-datajsonpath-1.1.5/bin/jsonpath0000755000004100000410000000110314567454057015734 0ustar www-datawww-data#!/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.1.5/.gitignore0000644000004100000410000000011714567454057015404 0ustar www-datawww-datapkg/* Gemfile.lock coverage/* doc/* .yardoc .DS_Store .idea vendor .tags *.gem jsonpath-1.1.5/.github/0000755000004100000410000000000014567454057014755 5ustar www-datawww-datajsonpath-1.1.5/.github/workflows/0000755000004100000410000000000014567454057017012 5ustar www-datawww-datajsonpath-1.1.5/.github/workflows/test.yml0000644000004100000410000000104514567454057020514 0ustar www-datawww-dataname: test on: - push - pull_request jobs: test: strategy: fail-fast: false matrix: ruby-version: - '2.6' - '2.7' - '3.2' - '3.1' - '3.0' - ruby-head - jruby-head - truffleruby-head runs-on: - ubuntu-latest runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - run: bundle exec rake test jsonpath-1.1.5/lib/0000755000004100000410000000000014567454057014163 5ustar www-datawww-datajsonpath-1.1.5/lib/jsonpath.rb0000644000004100000410000001026314567454057016340 0ustar www-datawww-data# frozen_string_literal: true require 'strscan' require 'multi_json' require 'jsonpath/proxy' require 'jsonpath/dig' require 'jsonpath/enumerable' require 'jsonpath/version' require 'jsonpath/parser' # JsonPath: initializes the class with a given JsonPath and parses that path # into a token array. class JsonPath PATH_ALL = '$..*' MAX_NESTING_ALLOWED = 100 DEFAULT_OPTIONS = { :default_path_leaf_to_null => false, :symbolize_keys => false, :use_symbols => false, :allow_send => true, :max_nesting => MAX_NESTING_ALLOWED } attr_accessor :path def initialize(path, opts = {}) @opts = DEFAULT_OPTIONS.merge(opts) set_max_nesting scanner = StringScanner.new(path.strip) @path = [] until scanner.eos? if (token = scanner.scan(/\$\B|@\B|\*|\.\./)) @path << token elsif (token = scanner.scan(/[$@\p{Alnum}:{}_ -]+/)) @path << "['#{token}']" elsif (token = scanner.scan(/'(.*?)'/)) @path << "[#{token}]" elsif (token = scanner.scan(/\[/)) @path << find_matching_brackets(token, scanner) elsif (token = scanner.scan(/\]/)) raise ArgumentError, 'unmatched closing bracket' elsif (token = scanner.scan(/\(.*\)/)) @path << token elsif scanner.scan(/\./) nil elsif (token = scanner.scan(/[><=] \d+/)) @path.last << token elsif (token = scanner.scan(/./)) @path.last << token else raise ArgumentError, "character '#{scanner.peek(1)}' not supported in query" 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 symbolize_keys?(opts) a.map! do |e| e.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v; } end end a end def self.fetch_all_path(obj) all_paths = ['$'] find_path(obj, '$', all_paths, obj.class == Array) return all_paths end def self.find_path(obj, root_key, all_paths, is_array = false) obj.each do |key, value| table_params = { key: key, root_key: root_key} is_loop = value.class == Array || value.class == Hash if is_loop path_exp = construct_path(table_params) all_paths << path_exp find_path(value, path_exp, all_paths, value.class == Array) elsif is_array table_params[:index] = obj.find_index(key) path_exp = construct_path(table_params) find_path(key, path_exp, all_paths, key.class == Array) if key.class == Hash || key.class == Array all_paths << path_exp else all_paths << construct_path(table_params) end end end def self.construct_path(table_row) if table_row[:index] return table_row[:root_key] + '['+ table_row[:index].to_s + ']' else return table_row[:root_key] + '.'+ table_row[:key] end 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, @opts), 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, opts = {}) obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str, max_nesting: opts[:max_nesting]) : obj_or_str end def deep_clone Marshal.load Marshal.dump(self) end def set_max_nesting return unless @opts[:max_nesting].is_a?(Integer) && @opts[:max_nesting] > MAX_NESTING_ALLOWED @opts[:max_nesting] = false end def symbolize_keys?(opts) opts.fetch(:symbolize_keys, @opts&.dig(:symbolize_keys)) end end jsonpath-1.1.5/lib/jsonpath/0000755000004100000410000000000014567454057016011 5ustar www-datawww-datajsonpath-1.1.5/lib/jsonpath/dig.rb0000644000004100000410000000260614567454057017105 0ustar www-datawww-data# frozen_string_literal: true class JsonPath module Dig # Similar to what Hash#dig or Array#dig def dig(context, *keys) keys.inject(context){|memo,k| dig_one(memo, k) } end # Returns a hash mapping each key from keys # to its dig value on context. def dig_as_hash(context, keys) keys.each_with_object({}) do |k, memo| memo[k] = dig_one(context, k) end end # Dig the value of k on context. def dig_one(context, k) case context when Hash context[@options[:use_symbols] ? k.to_sym : k] when Array context[k.to_i] else if context.respond_to?(:dig) context.dig(k) elsif @options[:allow_send] context.__send__(k) end end end # Yields the block if context has a diggable # value for k def yield_if_diggable(context, k, &blk) case context when Array nil when Hash k = @options[:use_symbols] ? k.to_sym : k return yield if context.key?(k) || @options[:default_path_leaf_to_null] else if context.respond_to?(:dig) digged = dig_one(context, k) yield if !digged.nil? || @options[:default_path_leaf_to_null] elsif @options[:allow_send] && context.respond_to?(k.to_s) && !Object.respond_to?(k.to_s) yield end end end end endjsonpath-1.1.5/lib/jsonpath/enumerable.rb0000644000004100000410000001225414567454057020461 0ustar www-datawww-data# frozen_string_literal: true class JsonPath class Enumerable include ::Enumerable include Dig 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 = key ? dig_one(context, key) : context @_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 '$' if node == @object each(context, key, pos + 1, &blk) else handle_wildcard(node, "['#{expr}']", context, key, pos, &blk) end when /^\[(.*)\]$/ handle_wildcard(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 dig_as_hash(context, keys) when Array context.each_with_object([]) do |c, memo| memo << dig_as_hash(c, keys) end end end def handle_wildcard(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] yield_if_diggable(node, k) do 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? next if node.nil? # when default_path_leaf_to_null is true next if node.size.zero? 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) case @mode when nil blk.call(key ? dig_one(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 = /@?(((?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 elsif operator.to_s.strip == '=~' parse_regex(t) else 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) dig(@_current_node, *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 # /foo/i -> Regex.new("foo", Regexp::IGNORECASE) without using eval # also supports %r{foo}i # following https://github.com/seamusabshere/to_regexp/blob/master/lib/to_regexp.rb def parse_regex(t) t =~ REGEX content = $1 || $3 options = $2 || $4 raise ArgumentError, "unsupported regex #{t} use /foo/ style" if !content || !options content = content.gsub '\\/', '/' flags = 0 flags |= Regexp::IGNORECASE if options.include?('i') flags |= Regexp::MULTILINE if options.include?('m') flags |= Regexp::EXTENDED if options.include?('x') # 'n' = none, 'e' = EUC, 's' = SJIS, 'u' = UTF-8 lang = options.scan(/[nes]/).join.downcase # ignores u since that is default and causes a warning args = [content, flags] args << lang unless lang.empty? # avoid warning Regexp.new(*args) end #  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 == '&&' next_value = bool_or_exp(top[index + 1]) res &&= next_value elsif item == '||' next_value = bool_or_exp(top[index + 1]) res ||= next_value 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.1.5/test/0000755000004100000410000000000014567454057014374 5ustar www-datawww-datajsonpath-1.1.5/test/test_jsonpath_bin.rb0000644000004100000410000000102114567454057020430 0ustar www-datawww-data# 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.1.5/test/test_jsonpath.rb0000644000004100000410000012043614567454057017614 0ustar www-datawww-data# 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) end def test_not_equals_operator expected = [ @object['store']['book'][0], @object['store']['book'][4], @object['store']['book'][5], @object['store']['book'][6] ] assert_equal(expected, JsonPath.new("$..book[?(@['category'] != 'fiction')]").on(@object)) assert_equal(expected, JsonPath.new("$..book[?(@['category']!=fiction)]").on(@object)) assert_equal(expected, JsonPath.new("$..book[?(@.category!=fiction)]").on(@object)) assert_equal(expected, 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) result = ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"] assert_equal result, JsonPath.new("$..book[?(@.price==13 || @.price==9 || @.price==23)].title").on(@object) assert_equal result, JsonPath.new("$..book[?(@.price==9 || @.price==23 || @.price==13)].title").on(@object) assert_equal result, JsonPath.new("$..book[?(@.price==23 || @.price==13 || @.price==9)].title").on(@object) end def test_or_operator_with_not_equals # Should be the same regardless of key style ( @.key vs @['key'] ) result = ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien', 'Lukyanenko'] assert_equal result, JsonPath.new("$..book[?(@['title']=='Osennie Vizity' || @['author']!='Lukyanenko')].author").on(@object) assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author != Lukyanenko )].author").on(@object) assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author!=Lukyanenko )].author").on(@object) end def test_and_operator assert_equal [], JsonPath.new("$..book[?(@['price'] == 13 && @['price'] == 23)]").on(@object) assert_equal [], JsonPath.new("$..book[?(@.price>=13 && @.category==fiction && @.title==no_match)]").on(@object) assert_equal [], JsonPath.new("$..book[?(@.title==no_match && @.category==fiction && @.price==13)]").on(@object) assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.title==no_match && @.category==fiction)]").on(@object) assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.bad_key_name==true && @.category==fiction)]").on(@object) expected = [@object['store']['book'][1]] assert_equal expected, JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object) assert_equal expected, JsonPath.new("$..book[?(@.price < 23 && @.price > 9)]").on(@object) expected = ['Sword of Honour', 'The Lord of the Rings'] assert_equal expected, JsonPath.new("$..book[?(@.price>=13 && @.category==fiction)].title").on(@object) assert_equal ['The Lord of the Rings'], JsonPath.new("$..book[?(@.category==fiction && @.isbn && @.price>9)].title").on(@object) assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.author=='Nigel Rees')].title").on(@object) assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.tags..asdf)].title").on(@object) end def test_and_operator_with_not_equals expected = ['Nigel Rees'] assert_equal expected, JsonPath.new("$..book[?(@['price']==9 && @['category']!=fiction)].author").on(@object) assert_equal expected, JsonPath.new("$..book[?(@.price==9 && @.category!=fiction)].author").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_object klass = Class.new{ attr_reader :b def initialize(b) @b = b end } object = klass.new("value") assert_equal ["value"], JsonPath.new('$.b').on(object) end def test_works_on_object_can_be_disabled klass = Class.new{ attr_reader :b def initialize(b) @b = b end } object = klass.new("value") assert_equal [], JsonPath.new('$.b', allow_send: false).on(object) end def test_works_on_diggable klass = Class.new{ attr_reader :h def initialize(h) @h = h end def dig(*keys) @h.dig(*keys) end } object = klass.new('a' => 'some', 'b' => 'value') assert_equal ['value'], JsonPath.new('$.b').on(object) object = { "foo" => klass.new('a' => 'some', 'b' => 'value') } assert_equal ['value'], JsonPath.new('$.foo.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_works_on_hash_with_summary object = { "foo" => [{ "a" => "some", "b" => "value" }] } assert_equal [{ "b" => "value" }], JsonPath.new("$.foo[*](b)").on(object) end def test_works_on_non_hash_with_summary klass = Struct.new(:a, :b) object = { "foo" => [klass.new("some", "value")] } assert_equal [{ "b" => "value" }], JsonPath.new("$.foo[*](b)").on(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 59, 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_support_for_umlauts_in_member_names assert_equal [@object['store']['Übermorgen']], JsonPath.new('$.store.Übermorgen').on(@object) end def test_support_for_spaces_in_member_name assert_equal [@object['store']['Title Case']], JsonPath.new('$.store.Title Case').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' }] } # These queries should be equivalent expected = [{ 'isTrue' => true, 'name' => 'testname1' }] assert_equal expected, JsonPath.new('$.data[?(@.isTrue)]').on(data) assert_equal expected, JsonPath.new('$.data[?(@.isTrue==true)]').on(data) assert_equal expected, JsonPath.new('$.data[?(@.isTrue == true)]').on(data) # These queries should be equivalent expected = [{ 'isTrue' => false, 'name' => 'testname2' }] assert_equal expected, JsonPath.new('$.data[?(@.isTrue != true)]').on(data) assert_equal expected, JsonPath.new('$.data[?(@.isTrue!=true)]').on(data) assert_equal expected, JsonPath.new('$.data[?(@.isTrue==false)]').on(data) end def test_and_operator_with_boolean_parameter_value data = { 'data' => [{ 'hasProperty1' => true, 'hasProperty2' => false, 'name' => 'testname1' }, { 'hasProperty1' => false, 'hasProperty2' => true, 'name' => 'testname2' }, { 'hasProperty1' => true, 'hasProperty2' => true, 'name' => 'testname3' }] } assert_equal ['testname3'], JsonPath.new('$.data[?(@.hasProperty1 && @.hasProperty2)].name').on(data) end def test_regex_simple assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@ =~ /asdf/)]').on(@object) assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@=~/asdf/)]').on(@object) end def test_regex_simple_miss assert_equal [], JsonPath.new('$.store.book..tags[?(@ =~ /wut/)]').on(@object) end def test_regex_r assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@ =~ %r{asdf})]').on(@object) end def test_regex_flags 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) end def test_regex_error assert_raises ArgumentError do JsonPath.new('$.store.book..tags[?(@ =~ asdf)]').on(@object) end 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_nested_with_unknown_key path = "$..[?(@.price == 9 || @.price == 33)].title" assert_equal ["Sayings of the Century", "Moby Dick", "Sayings of the Century", "Moby Dick"], JsonPath.new(path).on(@object) end def test_nested_with_unknown_key_filtered_array path = "$..[?(@['price'] == 9 || @['price'] == 33)].title" assert_equal ["Sayings of the Century", "Moby Dick", "Sayings of the Century", "Moby Dick"], JsonPath.new(path).on(@object) 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') || Gem::Version.new(RUBY_VERSION) > Gem::Version::new('2.6') 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_on_hash json = ' { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 } '.to_json assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, '$.(category,author)') end def test_selecting_multiple_keys_on_sub_hash skip("Failing as the semantics of .(x,y) is unclear") json = ' { "book": { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 } } '.to_json assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, '$.book.(category,author)') end def test_selecting_multiple_keys_on_array 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_on_array_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_use_symbol_opt 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 } ] } } on = ->(path){ JsonPath.on(json, path, use_symbols: true) } assert_equal ['reference', 'fiction'], on.("$.store.book[*].category") assert_equal ['reference', 'fiction'], on.("$..category") assert_equal ['reference'], on.("$.store.book[?(@['price'] == 8.95)].category") assert_equal [{'category' => 'reference'}], on.("$.store.book[?(@['price'] == 8.95)](category)") 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 test_index_access_by_number data = { '1': 'foo' } assert_equal ['foo'], JsonPath.new('$.1').on(data.to_json) end def test_behavior_on_null_and_missing data = { "foo" => nil, "bar" => { "baz" => nil }, "bars" => [ { "foo" => 12 }, { "foo" => nil }, { } ] } assert_equal [nil], JsonPath.new('$.foo').on(data) assert_equal [nil], JsonPath.new('$.bar.baz').on(data) assert_equal [], JsonPath.new('$.baz').on(data) assert_equal [], JsonPath.new('$.bar.foo').on(data) assert_equal [12, nil], JsonPath.new('$.bars[*].foo').on(data) end def test_default_path_leaf_to_null_opt data = { "foo" => nil, "bar" => { "baz" => nil }, "bars" => [ { "foo" => 12 }, { "foo" => nil }, { } ] } assert_equal [nil], JsonPath.new('$.foo', default_path_leaf_to_null: true).on(data) assert_equal [nil], JsonPath.new('$.bar.baz', default_path_leaf_to_null: true).on(data) assert_equal [nil], JsonPath.new('$.baz', default_path_leaf_to_null: true).on(data) assert_equal [nil], JsonPath.new('$.bar.foo', default_path_leaf_to_null: true).on(data) assert_equal [12, nil, nil], JsonPath.new('$.bars[*].foo', default_path_leaf_to_null: true).on(data) end def test_raise_max_nesting_error json = { a: { b: { c: { } } } }.to_json assert_raises(MultiJson::ParseError) { JsonPath.new('$.a', max_nesting: 1).on(json) } end def test_linefeed_in_path_error assert_raises(ArgumentError) { JsonPath.new("$.store\n.book") } end def test_with_max_nesting_false json = { a: { b: { c: { } } } }.to_json assert_equal [{}], JsonPath.new('$.a.b.c', max_nesting: false).on(json) end def test_initialize_with_max_nesting_exceeding_limit json = { a: { b: { c: { } } } }.to_json json_obj = JsonPath.new('$.a.b.c', max_nesting: 105) assert_equal [{}], json_obj.on(json) assert_equal false, json_obj.instance_variable_get(:@opts)[:max_nesting] end def test_initialize_without_max_nesting_exceeding_limit json_obj = JsonPath.new('$.a.b.c', max_nesting: 90) assert_equal 90, json_obj.instance_variable_get(:@opts)[:max_nesting] end def test_initialize_with_max_nesting_false_limit json_obj = JsonPath.new('$.a.b.c', max_nesting: false) assert_equal false, json_obj.instance_variable_get(:@opts)[:max_nesting] 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', 'Übermorgen' => 'The day after tomorrow', 'Title Case' => 'A title case string', '_links' => { 'self' => {} } } } end def test_fetch_all_path data = { "foo" => nil, "bar" => { "baz" => nil }, "bars" => [ { "foo" => 12 }, { "foo" => nil }, { } ] } assert_equal ["$", "$.foo", "$.bar", "$.bar.baz", "$.bars", "$.bars[0].foo", "$.bars[0]", "$.bars[1].foo", "$.bars[1]", "$.bars[2]"], JsonPath.fetch_all_path(data) end def test_extractore_with_dollar_key json = {"test" => {"$" =>"success", "a" => "123"}} assert_equal ["success"], JsonPath.on(json, "$.test.$") assert_equal ["123"], JsonPath.on(json, "$.test.a") end def test_symbolize_key data = { "store" => { "book" => [{"category" => "reference"}]}} assert_equal [{"category": "reference"}], JsonPath.new('$..book[0]', symbolize_keys: true).on(data) assert_equal [{"category": "reference"}], JsonPath.new('$..book[0]').on(data, symbolize_keys: true) end end jsonpath-1.1.5/test/test_readme.rb0000644000004100000410000001055214567454057017220 0ustar www-datawww-data# frozen_string_literal: true require 'minitest/autorun' require 'phocus' require 'jsonpath' require 'json' class TestJsonpathReadme < MiniTest::Unit::TestCase def setup @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 end attr_reader :json def test_library_section path = JsonPath.new('$..price') assert_equal [19.95, 8.95, 12.99, 8.99, 22.99], path.on(json) assert_equal [18.88], path.on('{"books":[{"title":"A Tale of Two Somethings","price":18.88}]}') assert_equal ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "Tolkien"], JsonPath.on(json, '$..author') 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(json) assert_equal [8.95, 8.99], JsonPath.new("$..price[?(@ < 10)]").on(json) assert_equal ["Sayings of the Century", "Moby Dick"], JsonPath.new("$..book[?(@['price'] == 8.95 || @['price'] == 8.99)].title").on(json) assert_equal [], JsonPath.new("$..book[?(@['price'] == 8.95 && @['price'] == 8.99)].title").on(json) assert_equal "red", JsonPath.new('$..color').first(json) end def test_library_section_enumerable enum = JsonPath.new('$..color')[json] assert_equal "red", enum.first assert enum.any?{ |c| c == 'red' } end def test_ruby_structures_section book = { title: "Sayings of the Century" } assert_equal [], JsonPath.new('$.title').on(book) assert_equal ["Sayings of the Century"], JsonPath.new('$.title', use_symbols: true).on(book) book_class = Struct.new(:title) book = book_class.new("Sayings of the Century") assert_equal ["Sayings of the Century"], JsonPath.new('$.title').on(book) book_class = Class.new{ attr_accessor :title } book = book_class.new book.title = "Sayings of the Century" assert_equal ["Sayings of the Century"], JsonPath.new('$.title', allow_send: true).on(book) end def test_options_section assert_equal ["0-553-21311-3", "0-395-19395-8"], JsonPath.new('$..book[*].isbn').on(json) assert_equal [nil, nil, "0-553-21311-3", "0-395-19395-8"], JsonPath.new('$..book[*].isbn', default_path_leaf_to_null: true).on(json) assert_equal ["price", "category", "title", "author"], JsonPath.new('$..book[0]').on(json).map(&:keys).flatten.uniq assert_equal [:price, :category, :title, :author], JsonPath.new('$..book[0]').on(json, symbolize_keys: true).map(&:keys).flatten.uniq end def selecting_value_section json = <<-HERE_DOC { "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 } ] } HERE_DOC got = JsonPath.on(json, "$.store.book[*](category,author)") expected = [ { "category" => "reference", "author" => "Nigel Rees" }, { "category" => "fiction", "author" => "Evelyn Waugh" } ] assert_equal expected, got end def test_manipulation_section assert_equal({"candy" => "big turks"}, JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash) json = '{"candy":"lollipop","noncandy":null,"other":"things"}' o = JsonPath.for(json). gsub('$..candy') {|v| "big turks" }. compact. delete('$..other'). to_hash assert_equal({"candy" => "big turks"}, o) end end jsonpath-1.1.5/.rspec0000644000004100000410000000004114567454057014525 0ustar www-datawww-data--colour --format doc --backtracejsonpath-1.1.5/Rakefile0000644000004100000410000000057014567454057015064 0ustar www-datawww-data# 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.1.5/LICENSE.md0000644000004100000410000000211114567454057015014 0ustar www-datawww-dataThe 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.1.5/.rubocop_todo.yml0000644000004100000410000000600514567454057016715 0ustar www-datawww-data# 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.1.5/Gemfile0000644000004100000410000000023714567454057014712 0ustar www-datawww-data# frozen_string_literal: true source 'http://rubygems.org' gemspec # gem 'rubocop', require: true, group: :test gem 'simplecov', require: false, group: :test jsonpath-1.1.5/README.md0000644000004100000410000001624514567454057014704 0ustar www-datawww-data# 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 reuse it later on some other object (thread safe) ... ```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", "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 ```ruby 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. ```ruby JsonPath.new('$..color').first(json) # => "red" ``` As well, we can directly create an `Enumerable` at any time using `#[]`. ```ruby enum = JsonPath.new('$..color')[json] # => # 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: ```ruby 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: ```ruby 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: ```ruby 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 ```ruby 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 ```ruby 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: ```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" } ] ``` ### 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"} ``` ### Fetch all paths To fetch all possible paths in given json, you can use `fetch_all_path`` method. data: ```bash { "store": { "book": [ { "category": "reference", "author": "Nigel Rees" }, { "category": "fiction", "author": "Evelyn Waugh" } ] } ``` ... and this query: ```ruby JsonPath.fetch_all_path(data) ``` ... the result will be: ```bash ["$", "$.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 ruby -Ilib:../lib test/test_jsonpath.rb --name test_wildcard_on_intermediary_element_v6 ``` jsonpath-1.1.5/jsonpath.gemspec0000644000004100000410000000214614567454057016613 0ustar www-datawww-data# 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 s.required_ruby_version = '>= 2.6' 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.test_files = `git ls-files`.split("\n").select { |f| f =~ /^spec/ } 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_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 'racc' s.add_development_dependency 'rake' end jsonpath-1.1.5/.rubocop.yml0000644000004100000410000000004014567454057015661 0ustar www-datawww-datainherit_from: .rubocop_todo.yml jsonpath-1.1.5/.gemtest0000644000004100000410000000000014567454057015054 0ustar www-datawww-data