# 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'][1]], JsonPath.new("$..book[?(@['year published'])]").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 61, 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_space_in_filter jsonld = { 'store' => [ { 'withoutspace' => 'urn' }, { 'with space' => 'urn:1' } ] } assert_equal([{ 'with space' => 'urn:1' }], JsonPath.new("$.store[?(@['with space'])]").on(jsonld)) end def test_hash_in_filter jsonld = { 'store' => [ { 'withouthash' => 'urn' }, { 'with #' => 'urn:1' } ] } assert_equal([{ 'with #' => 'urn:1' }], JsonPath.new("$.store[?(@['with #'])]").on(jsonld)) end def test_quote_in_filter jsonld = { 'store' => [ { 'withoutquote' => 'urn' }, { 'with \'' => 'urn:1' } ] } assert_equal([{ 'with \'' => 'urn:1' }], JsonPath.new("$.store[?(@['with \''])]").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], 'year published' => 1999 }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh', 'title' => 'Sword of Honour', 'price' => 13, 'year published' => 1952 }, { '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