jsonpathv2 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42c5754087dca72b09158229ea91cb5041026c27
4
+ data.tar.gz: c0d184648b89b424eb5e87a5d2ea4d034290ac2e
5
+ SHA512:
6
+ metadata.gz: 9fdb268247ff8e762fadad1875d9ce64f9c8884d72d7d2e1f4e35e06a5fa8fd0300b0ea1739931bfe0a4b675309db3675bc796692f457dba14adc51ed5bbac8e
7
+ data.tar.gz: be83d73bfa5b14d43f331067c99f806967f5f007f0bba985142be06bbf7af37391027abfd72cca38f62a781efb6ed2fa139c7b2e7b545f480eaffae39e38ddc5
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format doc
3
+ --backtrace
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ before_install:
2
+ - gem update --system 2.3.0
3
+ rvm:
4
+ # Disabling 1.9.3 build for now, until the issue with the
5
+ # native json gem is fixed.
6
+ # - 1.9.3
7
+ - 2.0.0
8
+ - 2.1.6
9
+ - 2.3.1
10
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'simplecov', :require => false, :group => :test
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Travis Build
2
+ [![Build Status](https://travis-ci.org/Skarlso/jsonpathv2.svg?branch=master)](https://travis-ci.org/Skarlso/jsonpathv2)
3
+
4
+ # JsonPath - Origin
5
+
6
+ This gem was forked, than re-written from this Gem: [JsonPath](https://github.com/joshbuddy/jsonpath). Since the original owner clearly abandoned that project, I took the liberty to fork it, and start fixing it. Please feel free to submit any issues you may encounter. PRs are very welcomed.
7
+
8
+ # JsonPath
9
+
10
+ This is an implementation of http://goessner.net/articles/JsonPath/.
11
+
12
+ ## What is JsonPath?
13
+
14
+ JsonPath is a way of addressing elements within a JSON object. Similar to xpath of yore, JsonPath lets you
15
+ traverse a json object and manipulate or access it.
16
+
17
+ ## Usage
18
+
19
+ ### Command-line
20
+
21
+ There is stand-alone usage through the binary `jsonpathv2`
22
+
23
+ jsonpathv2 [expression] (file|string)
24
+
25
+ If you omit the second argument, it will read stdin, assuming one valid JSON object
26
+ per line. Expression must be a valid jsonpathv2 expression.
27
+
28
+ ### Library
29
+
30
+ To use JsonPath as a library simply include and get goin'!
31
+
32
+ ~~~~~ {ruby}
33
+ require 'jsonpathv2'
34
+
35
+ json = <<-HERE_DOC
36
+ {"store":
37
+ {"bicycle":
38
+ {"price":19.95, "color":"red"},
39
+ "book":[
40
+ {"price":8.95, "category":"reference", "title":"Sayings of the Century", "author":"Nigel Rees"},
41
+ {"price":12.99, "category":"fiction", "title":"Sword of Honour", "author":"Evelyn Waugh"},
42
+ {"price":8.99, "category":"fiction", "isbn":"0-553-21311-3", "title":"Moby Dick", "author":"Herman Melville","color":"blue"},
43
+ {"price":22.99, "category":"fiction", "isbn":"0-395-19395-8", "title":"The Lord of the Rings", "author":"Tolkien"}
44
+ ]
45
+ }
46
+ }
47
+ HERE_DOC
48
+ ~~~~~
49
+
50
+ Now that we have a JSON object, let's get all the prices present in the object. We create an object for the path
51
+ in the following way.
52
+
53
+ ~~~~~ {ruby}
54
+ path = JsonPath.new('$..price')
55
+ ~~~~~
56
+
57
+ Now that we have a path, let's apply it to the object above.
58
+
59
+ ~~~~~ {ruby}
60
+ path.on(json)
61
+ # => [19.95, 8.95, 12.99, 8.99, 22.99]
62
+ ~~~~~
63
+
64
+ Or on some other object ...
65
+
66
+ ~~~~~ {ruby}
67
+ path.on('{"books":[{"title":"A Tale of Two Somethings","price":18.88}]}')
68
+ # => [18.88]
69
+ ~~~~~
70
+
71
+ You can also just combine this into one mega-call with the convenient `JsonPath.on` method.
72
+
73
+ ~~~~~ {ruby}
74
+ JsonPath.on(json, '$..author')
75
+ # => ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "Tolkien"]
76
+ ~~~~~
77
+
78
+ Of course the full JsonPath syntax is supported, such as array slices
79
+
80
+ ~~~~~ {ruby}
81
+ JsonPath.new('$..book[::2]').on(json)
82
+ # => [
83
+ # {"price"=>8.95, "category"=>"reference", "author"=>"Nigel Rees", "title"=>"Sayings of the Century"},
84
+ # {"price"=>8.99, "category"=>"fiction", "author"=>"Herman Melville", "title"=>"Moby Dick", "isbn"=>"0-553-21311-3"}
85
+ # ]
86
+ ~~~~~
87
+
88
+ ...and evals.
89
+
90
+ ~~~~~ {ruby}
91
+ JsonPath.new('$..price[?(@ < 10)]').on(json)
92
+ # => [8.95, 8.99]
93
+ ~~~~~
94
+
95
+ There is a convenience method, `#first` that gives you the first element for a JSON object and path.
96
+
97
+ ~~~~~ {ruby}
98
+ JsonPath.new('$..color').first(object)
99
+ # => "red"
100
+ ~~~~~
101
+
102
+ As well, we can directly create an `Enumerable` at any time using `#[]`.
103
+
104
+ ~~~~~ {ruby}
105
+ enum = JsonPath.new('$..color')[object]
106
+ # => #<JsonPath::Enumerable:...>
107
+ enum.first
108
+ # => "red"
109
+ enum.any?{ |c| c == 'red' }
110
+ # => true
111
+ ~~~~~
112
+
113
+ You can optionally prevent eval from being called on sub-expressions by passing in :allow_eval => false to the constructor.
114
+
115
+ ### Manipulation
116
+
117
+ If you'd like to do substitution in a json object, you can use `#gsub` or `#gsub!` to modify the object in place.
118
+
119
+ ~~~~~ {ruby}
120
+ JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash
121
+ ~~~~~
122
+
123
+ The result will be
124
+
125
+ ~~~~~ {ruby}
126
+ {'candy' => 'big turks'}
127
+ ~~~~~
128
+
129
+ 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:
130
+
131
+ ~~~~~ {ruby}
132
+ json = '{"candy":"lollipop","noncandy":null,"other":"things"}'
133
+ o = JsonPath.for(json).
134
+ gsub('$..candy') {|v| "big turks" }.
135
+ compact.
136
+ delete('$..other').
137
+ to_hash
138
+ # => {"candy" => "big turks"}
139
+ ~~~~~
data/ROADMAP.md ADDED
@@ -0,0 +1,13 @@
1
+ Roadmap for JsonPath
2
+ ====================
3
+
4
+
5
+ *0.0.3*
6
+ -------
7
+
8
+ * Refactor how tokens are handled in jsonpathv2.rb initialize.
9
+
10
+ *0.0.2*
11
+ -------
12
+
13
+ * Massive refactor of the huge complex case under Enumerable
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :test do
5
+ $LOAD_PATH << 'lib'
6
+ require 'minitest/autorun'
7
+ require 'phocus'
8
+ require 'jsonpathv2'
9
+ Dir['./test/**/test_*.rb'].each { |test| require test }
10
+ end
11
+
12
+ task default: :test
data/bin/jsonpathv2 ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jsonpathv2'
4
+ require 'multi_json'
5
+
6
+ def usage
7
+ puts "jsonpathv2 [expression] (file|string)
8
+
9
+ If you omit the second argument, it will read stdin, assuming one valid JSON
10
+ object per line. Expression must be a valid jsonpathv2 expression."
11
+ exit!
12
+ end
13
+
14
+ usage unless ARGV[0]
15
+
16
+ jsonpathv2 = JsonPath.new(ARGV[0])
17
+ case ARGV[1]
18
+ when nil # stdin
19
+ puts MultiJson.encode(jsonpathv2.on(MultiJson.decode(STDIN.read)))
20
+ when String
21
+ puts MultiJson.encode(jsonpathv2.on(MultiJson.decode(File.exist?(ARGV[1]) ? File.read(ARGV[1]) : ARGV[1])))
22
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.join(File.dirname(__FILE__), 'lib', 'jsonpathv2', 'version')
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'jsonpathv2'
7
+ s.version = JsonPath::VERSION
8
+ s.required_rubygems_version =
9
+ Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
10
+ s.authors = ['Gergely Brautigam']
11
+ s.summary = 'Ruby implementation of http://goessner.net/articles/JsonPath/'
12
+ s.description = 'Ruby implementation of http://goessner.net/articles/JsonPath/.'
13
+ s.email = 'skarlso777@gmail.com'
14
+ s.extra_rdoc_files = ['README.md']
15
+ s.files = `git ls-files`.split("\n")
16
+ s.homepage = 'https://github.com/Skarlso/jsonpathv2'
17
+ s.rdoc_options = ['--charset=UTF-8']
18
+ s.require_paths = ['lib']
19
+ s.rubygems_version = '1.3.7'
20
+ s.test_files = `git ls-files`.split("\n").select { |f| f =~ /^spec/ }
21
+ s.rubyforge_project = 'jsonpathv2'
22
+ s.executables = `git ls-files -- bin/*`.split("\n")
23
+ .map { |f| File.basename(f) }
24
+ s.licenses = ['MIT']
25
+
26
+ # dependencies
27
+ s.add_runtime_dependency 'multi_json'
28
+ s.add_development_dependency 'code_stats'
29
+ s.add_development_dependency 'rake'
30
+ s.add_development_dependency 'minitest', '~> 2.2.0'
31
+ s.add_development_dependency 'phocus'
32
+ s.add_development_dependency 'bundler'
33
+ end
@@ -0,0 +1,147 @@
1
+ class JsonPath
2
+ class Enumerable
3
+ include ::Enumerable
4
+ attr_reader :allow_eval
5
+ alias_method :allow_eval?, :allow_eval
6
+
7
+ def initialize(path, object, mode, options = nil)
8
+ @path = path.path
9
+ @object = object
10
+ @mode = mode
11
+ @options = options
12
+ @allow_eval = if @options && @options.key?(:allow_eval)
13
+ @options[:allow_eval]
14
+ else
15
+ true
16
+ end
17
+ end
18
+
19
+ def each(context = @object, key = nil, pos = 0, &blk)
20
+ node = key ? context[key] : context
21
+ @_current_node = node
22
+ return yield_value(blk, context, key) if pos == @path.size
23
+ case expr = @path[pos]
24
+ when '*', '..', '@'
25
+ each(context, key, pos + 1, &blk)
26
+ when '$'
27
+ each(context, key, pos + 1, &blk) if node == @object
28
+ when /^\[(.*)\]$/
29
+ expr[1, expr.size - 2].split(',').each do |sub_path|
30
+ case sub_path[0]
31
+ when '\'', '"'
32
+ if node.is_a?(Hash)
33
+ k = sub_path[1, sub_path.size - 2]
34
+ each(node, k, pos + 1, &blk) if node.key?(k)
35
+ end
36
+ when '?'
37
+ raise 'Cannot use ?(...) unless eval is enabled' unless allow_eval?
38
+ case node
39
+ when Array
40
+ node.size.times do |index|
41
+ @_current_node = node[index]
42
+ if process_function_or_literal(sub_path[1, sub_path.size - 1])
43
+ each(@_current_node, nil, pos + 1, &blk)
44
+ end
45
+ end
46
+ when Hash
47
+ if process_function_or_literal(sub_path[1, sub_path.size - 1])
48
+ each(@_current_node, nil, pos + 1, &blk)
49
+ end
50
+ else
51
+ yield node if process_function_or_literal(sub_path[1, sub_path.size - 1])
52
+ end
53
+ else
54
+ if node.is_a?(Array)
55
+ next if node.empty?
56
+ array_args = sub_path.split(':')
57
+ if array_args[0] == '*'
58
+ start_idx = 0
59
+ end_idx = node.size - 1
60
+ else
61
+ start_idx = process_function_or_literal(array_args[0], 0)
62
+ next unless start_idx
63
+ end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1))
64
+ next unless end_idx
65
+ if start_idx == end_idx
66
+ next unless start_idx < node.size
67
+ end
68
+ end
69
+ start_idx %= node.size
70
+ end_idx %= node.size
71
+ step = process_function_or_literal(array_args[2], 1)
72
+ next unless step
73
+ (start_idx..end_idx).step(step) { |i| each(node, i, pos + 1, &blk) }
74
+ end
75
+ end
76
+ end
77
+ else
78
+ if pos == (@path.size - 1) && node && allow_eval?
79
+ if eval("node #{@path[pos]}")
80
+ yield_value(blk, context, key)
81
+ end
82
+ end
83
+ end
84
+
85
+ if pos > 0 && @path[pos - 1] == '..'
86
+ case node
87
+ when Hash then node.each { |k, _| each(node, k, pos, &blk) }
88
+ when Array then node.each_with_index { |_, i| each(node, i, pos, &blk) }
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def yield_value(blk, context, key)
96
+ case @mode
97
+ when nil
98
+ blk.call(key ? context[key] : context)
99
+ when :compact
100
+ context.delete(key) if key && context[key].nil?
101
+ when :delete
102
+ context.delete(key) if key
103
+ when :substitute
104
+ if key
105
+ context[key] = blk.call(context[key])
106
+ else
107
+ context.replace(blk.call(context[key]))
108
+ end
109
+ end
110
+ end
111
+
112
+ def process_function_or_literal(exp, default = nil)
113
+ return default if exp.nil? || exp.empty?
114
+ return Integer(exp) if exp[0] != '('
115
+ return nil unless allow_eval? && @_current_node
116
+
117
+ identifiers = /@?(\.(\w+))+/.match(exp)
118
+
119
+ if !identifiers.nil? &&
120
+ !@_current_node.methods.include?(identifiers[2].to_sym)
121
+
122
+ exp_to_eval = exp.dup
123
+ exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
124
+ el == '@' ? '@_current_node' : "['#{el}']"
125
+ end.join
126
+
127
+ begin
128
+ return eval(exp_to_eval)
129
+ # if eval failed because of bad arguments or missing methods
130
+ rescue StandardError
131
+ return default
132
+ end
133
+ end
134
+ # otherwise eval as is
135
+ # TODO: this eval is wrong, because hash accessor could be nil and nil
136
+ # cannot be compared with anything, for instance,
137
+ # @a_current_node['price'] - we can't be sure that 'price' are in every
138
+ # node, but it's only in several nodes I wrapped this eval into rescue
139
+ # returning false when error, but this eval should be refactored.
140
+ begin
141
+ eval(exp.gsub(/@/, '@_current_node'))
142
+ rescue
143
+ false
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,55 @@
1
+ class JsonPath
2
+ class Proxy
3
+ attr_reader :obj
4
+ alias_method :to_hash, :obj
5
+
6
+ def initialize(obj)
7
+ @obj = obj
8
+ end
9
+
10
+ def gsub(path, replacement = nil, &replacement_block)
11
+ _gsub(_deep_copy, path, replacement ? proc { replacement } : replacement_block)
12
+ end
13
+
14
+ def gsub!(path, replacement = nil, &replacement_block)
15
+ _gsub(@obj, path, replacement ? proc { replacement } : replacement_block)
16
+ end
17
+
18
+ def delete(path = JsonPath::PATH_ALL)
19
+ _delete(_deep_copy, path)
20
+ end
21
+
22
+ def delete!(path = JsonPath::PATH_ALL)
23
+ _delete(@obj, path)
24
+ end
25
+
26
+ def compact(path = JsonPath::PATH_ALL)
27
+ _compact(_deep_copy, path)
28
+ end
29
+
30
+ def compact!(path = JsonPath::PATH_ALL)
31
+ _compact(@obj, path)
32
+ end
33
+
34
+ private
35
+
36
+ def _deep_copy
37
+ Marshal.load(Marshal.dump(@obj))
38
+ end
39
+
40
+ def _gsub(obj, path, replacement)
41
+ JsonPath.new(path)[obj, :substitute].each(&replacement)
42
+ Proxy.new(obj)
43
+ end
44
+
45
+ def _delete(obj, path)
46
+ JsonPath.new(path)[obj, :delete].each
47
+ Proxy.new(obj)
48
+ end
49
+
50
+ def _compact(obj, path)
51
+ JsonPath.new(path)[obj, :compact].each
52
+ Proxy.new(obj)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ class JsonPath
2
+ VERSION = '0.0.2'.freeze
3
+ end
data/lib/jsonpathv2.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'strscan'
2
+ require 'multi_json'
3
+ require 'jsonpathv2/proxy'
4
+ require 'jsonpathv2/enumerable'
5
+ require 'jsonpathv2/version'
6
+
7
+ class JsonPath
8
+ PATH_ALL = '$..*'.freeze
9
+
10
+ attr_accessor :path
11
+
12
+ def initialize(path, opts = nil)
13
+ @opts = opts
14
+ scanner = StringScanner.new(path)
15
+ @path = []
16
+ until scanner.eos?
17
+ if token = scanner.scan(/\$|@|\*|\.\./)
18
+ @path << token
19
+ elsif token = scanner.scan(/[a-zA-Z0-9_-]+/)
20
+ @path << "['#{token}']"
21
+ elsif token = scanner.scan(/'(.*?)'/)
22
+ @path << "[#{token}]"
23
+ elsif token = scanner.scan(/\[/)
24
+ @path << find_matching_brackets(token, scanner)
25
+ elsif token = scanner.scan(/\]/)
26
+ raise ArgumentError, 'unmatched closing bracket'
27
+ elsif scanner.scan(/\./)
28
+ nil
29
+ elsif token = scanner.scan(/[><=] \d+/)
30
+ @path.last << token
31
+ elsif token = scanner.scan(/./)
32
+ @path.last << token
33
+ end
34
+ end
35
+ end
36
+
37
+ def find_matching_brackets(token, scanner)
38
+ count = 1
39
+ until count.zero?
40
+ if t = scanner.scan(/\[/)
41
+ token << t
42
+ count += 1
43
+ elsif t = scanner.scan(/\]/)
44
+ token << t
45
+ count -= 1
46
+ elsif t = scanner.scan(/[^\[\]]+/)
47
+ token << t
48
+ elsif scanner.eos?
49
+ raise ArgumentError, 'unclosed bracket'
50
+ end
51
+ end
52
+ token
53
+ end
54
+
55
+ def join(join_path)
56
+ res = deep_clone
57
+ res.path += JsonPath.new(join_path).path
58
+ res
59
+ end
60
+
61
+ def on(obj_or_str)
62
+ enum_on(obj_or_str).to_a
63
+ end
64
+
65
+ def first(obj_or_str, *args)
66
+ enum_on(obj_or_str).first(*args)
67
+ end
68
+
69
+ def enum_on(obj_or_str, mode = nil)
70
+ JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
71
+ @opts)
72
+ end
73
+ alias_method :[], :enum_on
74
+
75
+ def self.on(obj_or_str, path, opts = nil)
76
+ new(path, opts).on(process_object(obj_or_str))
77
+ end
78
+
79
+ def self.for(obj_or_str)
80
+ Proxy.new(process_object(obj_or_str))
81
+ end
82
+
83
+ private
84
+
85
+ def self.process_object(obj_or_str)
86
+ obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str
87
+ end
88
+
89
+ def deep_clone
90
+ Marshal.load Marshal.dump(self)
91
+ end
92
+ end
@@ -0,0 +1,236 @@
1
+ class TestJsonpath < MiniTest::Unit::TestCase
2
+
3
+ def setup
4
+ @object = example_object
5
+ @object2 = example_object
6
+ end
7
+
8
+ def test_bracket_matching
9
+ assert_raises(ArgumentError) {
10
+ JsonPath.new('$.store.book[0')
11
+ }
12
+ assert_raises(ArgumentError) {
13
+ JsonPath.new('$.store.book[0]]')
14
+ }
15
+ assert_equal [9], JsonPath.new('$.store.book[0].price').on(@object)
16
+ end
17
+
18
+ def test_lookup_direct_path
19
+ assert_equal 7, JsonPath.new('$.store.*').on(@object).first['book'].size
20
+ end
21
+
22
+ def test_lookup_missing_element
23
+ assert_equal [], JsonPath.new('$.store.book[99].price').on(@object)
24
+ end
25
+
26
+ def test_retrieve_all_authors
27
+ assert_equal [
28
+ @object['store']['book'][0]['author'],
29
+ @object['store']['book'][1]['author'],
30
+ @object['store']['book'][2]['author'],
31
+ @object['store']['book'][3]['author'],
32
+ @object['store']['book'][4]['author'],
33
+ @object['store']['book'][5]['author'],
34
+ @object['store']['book'][6]['author']
35
+ ], JsonPath.new('$..author').on(@object)
36
+ end
37
+
38
+ def test_retrieve_all_prices
39
+ assert_equal [
40
+ @object['store']['bicycle']['price'],
41
+ @object['store']['book'][0]['price'],
42
+ @object['store']['book'][1]['price'],
43
+ @object['store']['book'][2]['price'],
44
+ @object['store']['book'][3]['price']
45
+ ].sort, JsonPath.new('$..price').on(@object).sort
46
+ end
47
+
48
+ def test_recognize_array_splices
49
+ assert_equal [@object['store']['book'][0], @object['store']['book'][1]], JsonPath.new('$..book[0:1:1]').on(@object)
50
+ assert_equal [@object['store']['book'][1], @object['store']['book'][3], @object['store']['book'][5]], JsonPath.new('$..book[1::2]').on(@object)
51
+ assert_equal [@object['store']['book'][0], @object['store']['book'][2], @object['store']['book'][4], @object['store']['book'][6]], JsonPath.new('$..book[::2]').on(@object)
52
+ assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new('$..book[:-5:2]').on(@object)
53
+ assert_equal [@object['store']['book'][5], @object['store']['book'][6]], JsonPath.new('$..book[5::]').on(@object)
54
+ end
55
+
56
+ def test_recognize_array_comma
57
+ assert_equal [@object['store']['book'][0], @object['store']['book'][1]], JsonPath.new('$..book[0,1]').on(@object)
58
+ assert_equal [@object['store']['book'][2], @object['store']['book'][6]], JsonPath.new('$..book[2,-1::]').on(@object)
59
+ end
60
+
61
+ def test_recognize_filters
62
+ assert_equal [@object['store']['book'][2], @object['store']['book'][3]], JsonPath.new("$..book[?(@['isbn'])]").on(@object)
63
+ assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] < 10)]").on(@object)
64
+ assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] == 9)]").on(@object)
65
+ assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] > 20)]").on(@object)
66
+ end
67
+
68
+ if RUBY_VERSION[/^1\.9/]
69
+ def test_recognize_filters_on_val
70
+ assert_equal [@object['store']['book'][1]['price'], @object['store']['book'][3]['price'], @object['store']['bicycle']['price']], JsonPath.new("$..price[?(@ > 10)]").on(@object)
71
+ end
72
+ end
73
+
74
+ def test_no_eval
75
+ assert_equal [], JsonPath.new('$..book[(@.length-2)]', :allow_eval => false).on(@object)
76
+ end
77
+
78
+ def test_paths_with_underscores
79
+ assert_equal [@object['store']['bicycle']['catalogue_number']], JsonPath.new('$.store.bicycle.catalogue_number').on(@object)
80
+ end
81
+
82
+ def test_path_with_hyphens
83
+ assert_equal [@object['store']['bicycle']['single-speed']], JsonPath.new('$.store.bicycle.single-speed').on(@object)
84
+ end
85
+
86
+ def test_paths_with_numbers
87
+ assert_equal [@object['store']['bicycle']['2seater']], JsonPath.new('$.store.bicycle.2seater').on(@object)
88
+ end
89
+
90
+ def test_recognize_array_with_evald_index
91
+ assert_equal [@object['store']['book'][2]], JsonPath.new('$..book[(@.length-5)]').on(@object)
92
+ end
93
+
94
+ def test_use_first
95
+ assert_equal @object['store']['book'][2], JsonPath.new('$..book[(@.length-5)]').first(@object)
96
+ end
97
+
98
+ def test_counting
99
+ assert_equal 49, JsonPath.new('$..*').on(@object).to_a.size
100
+ end
101
+
102
+ def test_space_in_path
103
+ assert_equal ['e'], JsonPath.new("$.'c d'").on({"a" => "a","b" => "b", "c d" => "e"})
104
+ end
105
+
106
+ def test_class_method
107
+ assert_equal JsonPath.new('$..author').on(@object), JsonPath.on(@object, '$..author')
108
+ end
109
+
110
+ def test_join
111
+ assert_equal JsonPath.new('$.store.book..author').on(@object), JsonPath.new('$.store').join('book..author').on(@object)
112
+ end
113
+
114
+ def test_gsub
115
+ @object2['store']['bicycle']['price'] += 10
116
+ @object2['store']['book'][0]['price'] += 10
117
+ @object2['store']['book'][1]['price'] += 10
118
+ @object2['store']['book'][2]['price'] += 10
119
+ @object2['store']['book'][3]['price'] += 10
120
+ assert_equal @object2, JsonPath.for(@object).gsub('$..price') { |p| p + 10 }.to_hash
121
+ end
122
+
123
+ def test_gsub!
124
+ JsonPath.for(@object).gsub!('$..price') { |p| p + 10 }
125
+ assert_equal 30, @object['store']['bicycle']['price']
126
+ assert_equal 19, @object['store']['book'][0]['price']
127
+ assert_equal 23, @object['store']['book'][1]['price']
128
+ assert_equal 19, @object['store']['book'][2]['price']
129
+ assert_equal 33, @object['store']['book'][3]['price']
130
+ end
131
+
132
+ def test_weird_gsub!
133
+ h = {'hi' => 'there'}
134
+ JsonPath.for(@object).gsub!('$.*') { |n| h }
135
+ assert_equal h, @object
136
+ end
137
+
138
+ def test_compact
139
+ h = {'hi' => 'there', 'you' => nil}
140
+ JsonPath.for(h).compact!
141
+ assert_equal({'hi' => 'there'}, h)
142
+ end
143
+
144
+ def test_delete
145
+ h = {'hi' => 'there', 'you' => nil}
146
+ JsonPath.for(h).delete!('*.hi')
147
+ assert_equal({'you' => nil}, h)
148
+ end
149
+
150
+ def test_wildcard
151
+ assert_equal @object['store']['book'].collect{|e| e['price']}.compact, JsonPath.on(@object, '$..book[*].price')
152
+ end
153
+
154
+ def test_wildcard_empty_array
155
+ object = @object.merge("bicycle" => { "tire" => [] })
156
+ assert_equal [], JsonPath.on(object, "$..bicycle.tire[*]")
157
+ end
158
+
159
+ def test_support_filter_by_array_childnode_value
160
+ assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@.price > 20)]").on(@object)
161
+ end
162
+
163
+ def test_support_filter_by_childnode_value_with_inconsistent_children
164
+ @object['store']['book'][0] = "string_instead_of_object"
165
+ assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@.price > 20)]").on(@object)
166
+ end
167
+
168
+ def test_support_filter_by_childnode_value_and_select_child_key
169
+ assert_equal [23], JsonPath.new("$..book[?(@.price > 20)].price").on(@object)
170
+ end
171
+
172
+ def test_support_filter_by_childnode_value_over_childnode_and_select_child_key
173
+ assert_equal ["Osennie Vizity"], JsonPath.new("$..book[?(@.written.year == 1996)].title").on(@object)
174
+ end
175
+
176
+ def test_support_filter_by_object_childnode_value
177
+ data = {
178
+ "data" => {
179
+ "type" => "users",
180
+ "id" => "123"
181
+ }
182
+ }
183
+ assert_equal [{"type"=>"users", "id"=>"123"}], JsonPath.new("$.data[?(@.type == 'users')]").on(data)
184
+ assert_equal [], JsonPath.new("$.data[?(@.type == 'admins')]").on(data)
185
+ end
186
+
187
+ def example_object
188
+ { 'store' => {
189
+ 'book' => [
190
+ { 'category' => 'reference',
191
+ 'author' => 'Nigel Rees',
192
+ 'title' => 'Sayings of the Century',
193
+ 'price' => 9 },
194
+ { 'category' => 'fiction',
195
+ 'author' => 'Evelyn Waugh',
196
+ 'title' => 'Sword of Honour',
197
+ 'price' => 13 },
198
+ { 'category' => 'fiction',
199
+ 'author' => 'Herman Melville',
200
+ 'title' => 'Moby Dick',
201
+ 'isbn' => '0-553-21311-3',
202
+ 'price' => 9 },
203
+ { 'category' => 'fiction',
204
+ 'author' => 'J. R. R. Tolkien',
205
+ 'title' => 'The Lord of the Rings',
206
+ 'isbn' => '0-395-19395-8',
207
+ 'price' => 23 },
208
+ { 'category' => 'russian_fiction',
209
+ 'author' => 'Lukyanenko',
210
+ 'title' => 'Imperatory Illuziy',
211
+ 'written' => {
212
+ 'year' => 1995
213
+ } },
214
+ { 'category' => 'russian_fiction',
215
+ 'author' => 'Lukyanenko',
216
+ 'title' => 'Osennie Vizity',
217
+ 'written' => {
218
+ 'year' => 1996
219
+ } },
220
+ { 'category' => 'russian_fiction',
221
+ 'author' => 'Lukyanenko',
222
+ 'title' => 'Ne vremya dlya drakonov',
223
+ 'written' => {
224
+ 'year' => 1997
225
+ } }
226
+ ],
227
+ 'bicycle' => {
228
+ 'color' => 'red',
229
+ 'price' => 20,
230
+ 'catalogue_number' => 123_45,
231
+ 'single-speed' => 'no',
232
+ '2seater' => 'yes'
233
+ }
234
+ } }
235
+ end
236
+ end
@@ -0,0 +1,21 @@
1
+ class TestJsonpathBin < MiniTest::Unit::TestCase
2
+ def setup
3
+ @runner = 'ruby -Ilib bin/jsonpathv2'
4
+ @original_dir = Dir.pwd
5
+ Dir.chdir(File.join(File.dirname(__FILE__), '..'))
6
+ end
7
+
8
+ def teardown
9
+ Dir.chdir(@original_dir)
10
+ `rm /tmp/test.json`
11
+ end
12
+
13
+ def test_stdin
14
+ assert_equal '["time"]', `echo '{"test": "time"}' | #{@runner} '$.test'`.strip
15
+ end
16
+
17
+ def test_stdin
18
+ File.open('/tmp/test.json', 'w') { |f| f << '{"test": "time"}' }
19
+ assert_equal '["time"]', `#{@runner} '$.test' /tmp/test.json`.strip
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonpathv2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Gergely Brautigam
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: multi_json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: code_stats
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.2.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: phocus
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Ruby implementation of http://goessner.net/articles/JsonPath/.
98
+ email: skarlso777@gmail.com
99
+ executables:
100
+ - jsonpathv2
101
+ extensions: []
102
+ extra_rdoc_files:
103
+ - README.md
104
+ files:
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - README.md
109
+ - ROADMAP.md
110
+ - Rakefile
111
+ - bin/jsonpathv2
112
+ - jsonpathv2.gemspec
113
+ - lib/jsonpathv2.rb
114
+ - lib/jsonpathv2/enumerable.rb
115
+ - lib/jsonpathv2/proxy.rb
116
+ - lib/jsonpathv2/version.rb
117
+ - test/test_jsonpath.rb
118
+ - test/test_jsonpath_bin.rb
119
+ homepage: https://github.com/Skarlso/jsonpathv2
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options:
125
+ - "--charset=UTF-8"
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project: jsonpathv2
140
+ rubygems_version: 2.6.6
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Ruby implementation of http://goessner.net/articles/JsonPath/
144
+ test_files: []