jsonpathv2 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []