jsonpath 0.9.3 → 1.1.5

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.
@@ -3,6 +3,7 @@
3
3
  class JsonPath
4
4
  class Enumerable
5
5
  include ::Enumerable
6
+ include Dig
6
7
 
7
8
  def initialize(path, object, mode, options = {})
8
9
  @path = path.path
@@ -12,16 +13,25 @@ class JsonPath
12
13
  end
13
14
 
14
15
  def each(context = @object, key = nil, pos = 0, &blk)
15
- node = key ? context[key] : context
16
+ node = key ? dig_one(context, key) : context
16
17
  @_current_node = node
17
18
  return yield_value(blk, context, key) if pos == @path.size
19
+
18
20
  case expr = @path[pos]
19
21
  when '*', '..', '@'
20
22
  each(context, key, pos + 1, &blk)
21
23
  when '$'
22
- each(context, key, pos + 1, &blk) if node == @object
24
+ if node == @object
25
+ each(context, key, pos + 1, &blk)
26
+ else
27
+ handle_wildcard(node, "['#{expr}']", context, key, pos, &blk)
28
+ end
23
29
  when /^\[(.*)\]$/
24
- handle_wildecard(node, expr, context, key, pos, &blk)
30
+ handle_wildcard(node, expr, context, key, pos, &blk)
31
+ when /\(.*\)/
32
+ keys = expr.gsub(/[()]/, '').split(',').map(&:strip)
33
+ new_context = filter_context(context, keys)
34
+ yield_value(blk, new_context, key)
25
35
  end
26
36
 
27
37
  if pos > 0 && @path[pos - 1] == '..' || (@path[pos - 1] == '*' && @path[pos] != '..')
@@ -34,34 +44,54 @@ class JsonPath
34
44
 
35
45
  private
36
46
 
37
- def handle_wildecard(node, expr, _context, _key, pos, &blk)
47
+ def filter_context(context, keys)
48
+ case context
49
+ when Hash
50
+ dig_as_hash(context, keys)
51
+ when Array
52
+ context.each_with_object([]) do |c, memo|
53
+ memo << dig_as_hash(c, keys)
54
+ end
55
+ end
56
+ end
57
+
58
+ def handle_wildcard(node, expr, _context, _key, pos, &blk)
38
59
  expr[1, expr.size - 2].split(',').each do |sub_path|
39
60
  case sub_path[0]
40
61
  when '\'', '"'
41
- if node.is_a?(Hash)
42
- k = sub_path[1, sub_path.size - 2]
43
- node[k] ||= nil if @options[:default_path_leaf_to_null]
44
- each(node, k, pos + 1, &blk) if node.key?(k)
62
+ k = sub_path[1, sub_path.size - 2]
63
+ yield_if_diggable(node, k) do
64
+ each(node, k, pos + 1, &blk)
45
65
  end
46
66
  when '?'
47
67
  handle_question_mark(sub_path, node, pos, &blk)
48
68
  else
49
69
  next if node.is_a?(Array) && node.empty?
70
+ next if node.nil? # when default_path_leaf_to_null is true
71
+ next if node.size.zero?
72
+
50
73
  array_args = sub_path.split(':')
51
74
  if array_args[0] == '*'
52
75
  start_idx = 0
53
76
  end_idx = node.size - 1
77
+ elsif sub_path.count(':') == 0
78
+ start_idx = end_idx = process_function_or_literal(array_args[0], 0)
79
+ next unless start_idx
80
+ next if start_idx >= node.size
54
81
  else
55
82
  start_idx = process_function_or_literal(array_args[0], 0)
56
83
  next unless start_idx
57
- end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1))
84
+
85
+ end_idx = array_args[1] && ensure_exclusive_end_index(process_function_or_literal(array_args[1], -1)) || -1
58
86
  next unless end_idx
59
87
  next if start_idx == end_idx && start_idx >= node.size
60
88
  end
89
+
61
90
  start_idx %= node.size
62
91
  end_idx %= node.size
63
92
  step = process_function_or_literal(array_args[2], 1)
64
93
  next unless step
94
+
65
95
  if @mode == :delete
66
96
  (start_idx..end_idx).step(step) { |i| node[i] = nil }
67
97
  node.compact!
@@ -72,13 +102,17 @@ class JsonPath
72
102
  end
73
103
  end
74
104
 
105
+ def ensure_exclusive_end_index(value)
106
+ return value unless value.is_a?(Integer) && value > 0
107
+
108
+ value - 1
109
+ end
110
+
75
111
  def handle_question_mark(sub_path, node, pos, &blk)
76
112
  case node
77
113
  when Array
78
114
  node.size.times do |index|
79
115
  @_current_node = node[index]
80
- # exps = sub_path[1, sub_path.size - 1]
81
- # if @_current_node.send("[#{exps.gsub(/@/, '@_current_node')}]")
82
116
  if process_function_or_literal(sub_path[1, sub_path.size - 1])
83
117
  each(@_current_node, nil, pos + 1, &blk)
84
118
  end
@@ -93,10 +127,9 @@ class JsonPath
93
127
  end
94
128
 
95
129
  def yield_value(blk, context, key)
96
- key = Integer(key) rescue key if key
97
130
  case @mode
98
131
  when nil
99
- blk.call(key ? context[key] : context)
132
+ blk.call(key ? dig_one(context, key) : context)
100
133
  when :compact
101
134
  if key && context[key].nil?
102
135
  key.is_a?(Integer) ? context.delete_at(key) : context.delete(key)
@@ -104,6 +137,8 @@ class JsonPath
104
137
  when :delete
105
138
  if key
106
139
  key.is_a?(Integer) ? context.delete_at(key) : context.delete(key)
140
+ else
141
+ context.replace({})
107
142
  end
108
143
  when :substitute
109
144
  if key
@@ -119,19 +154,17 @@ class JsonPath
119
154
  return Integer(exp) if exp[0] != '('
120
155
  return nil unless @_current_node
121
156
 
122
- identifiers = /@?((?<!\d)\.(?!\d)(\w+))+/.match(exp)
123
- if !identifiers.nil? && !@_current_node.methods.include?(identifiers[2].to_sym)
157
+ identifiers = /@?(((?<!\d)\.(?!\d)(\w+))|\['(.*?)'\])+/.match(exp)
158
+ # to filter arrays with known/unknown name.
159
+ if (!identifiers.nil? && !(@_current_node.methods.include?(identifiers[2]&.to_sym) || @_current_node.methods.include?(identifiers[4]&.to_sym)))
124
160
  exp_to_eval = exp.dup
125
- exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
126
- el == '@' ? '@' : "['#{el}']"
127
- end.join
128
161
  begin
129
- return JsonPath::Parser.new(@_current_node).parse(exp_to_eval)
162
+ return JsonPath::Parser.new(@_current_node, @options).parse(exp_to_eval)
130
163
  rescue StandardError
131
164
  return default
132
165
  end
133
166
  end
134
- JsonPath::Parser.new(@_current_node).parse(exp)
167
+ JsonPath::Parser.new(@_current_node, @options).parse(exp)
135
168
  end
136
169
  end
137
170
  end
@@ -1,67 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'strscan'
4
- require 'to_regexp'
5
4
 
6
5
  class JsonPath
7
6
  # Parser parses and evaluates an expression passed to @_current_node.
8
7
  class Parser
9
- def initialize(node)
8
+ include Dig
9
+
10
+ REGEX = /\A\/(.+)\/([imxnesu]*)\z|\A%r{(.+)}([imxnesu]*)\z/
11
+
12
+ def initialize(node, options)
10
13
  @_current_node = node
14
+ @_expr_map = {}
15
+ @options = options
11
16
  end
12
17
 
18
+ # parse will parse an expression in the following way.
19
+ # Split the expression up into an array of legs for && and || operators.
20
+ # Parse this array into a map for which the keys are the parsed legs
21
+ #  of the split. This map is then used to replace the expression with their
22
+ # corresponding boolean or numeric value. This might look something like this:
23
+ # ((false || false) && (false || true))
24
+ #  Once this string is assembled... we proceed to evaluate from left to right.
25
+ #  The above string is broken down like this:
26
+ # (false && (false || true))
27
+ # (false && true)
28
+ #  false
13
29
  def parse(exp)
14
30
  exps = exp.split(/(&&)|(\|\|)/)
15
- ret = parse_exp(exps.shift)
16
- exps.each_with_index do |item, index|
17
- case item
18
- when '&&'
19
- ret &&= parse_exp(exps[index + 1])
20
- when '||'
21
- ret ||= parse_exp(exps[index + 1])
22
- end
31
+ construct_expression_map(exps)
32
+ @_expr_map.each { |k, v| exp.sub!(k, v.to_s) }
33
+ raise ArgumentError, "unmatched parenthesis in expression: #{exp}" unless check_parenthesis_count(exp)
34
+
35
+ exp = parse_parentheses(exp) while exp.include?('(')
36
+ bool_or_exp(exp)
37
+ end
38
+
39
+ # Construct a map for which the keys are the expressions
40
+ #  and the values are the corresponding parsed results.
41
+ # Exp.:
42
+ # {"(@['author'] =~ /herman|lukyanenko/i)"=>0}
43
+ # {"@['isTrue']"=>true}
44
+ def construct_expression_map(exps)
45
+ exps.each_with_index do |item, _index|
46
+ next if item == '&&' || item == '||'
47
+
48
+ item = item.strip.gsub(/\)*$/, '').gsub(/^\(*/, '')
49
+ @_expr_map[item] = parse_exp(item)
23
50
  end
24
- ret
25
51
  end
26
52
 
53
+ # Using a scanner break down the individual expressions and determine if
54
+ # there is a match in the JSON for it or not.
27
55
  def parse_exp(exp)
28
56
  exp = exp.sub(/@/, '').gsub(/^\(/, '').gsub(/\)$/, '').tr('"', '\'').strip
57
+ exp.scan(/^\[(\d+)\]/) do |i|
58
+ next if i.empty?
59
+
60
+ index = Integer(i[0])
61
+ raise ArgumentError, 'Node does not appear to be an array.' unless @_current_node.is_a?(Array)
62
+ raise ArgumentError, "Index out of bounds for nested array. Index: #{index}" if @_current_node.size < index
63
+
64
+ @_current_node = @_current_node[index]
65
+ # Remove the extra '' and the index.
66
+ exp = exp.gsub(/^\[\d+\]|\[''\]/, '')
67
+ end
29
68
  scanner = StringScanner.new(exp)
30
69
  elements = []
31
70
  until scanner.eos?
32
- if scanner.scan(/\./)
33
- sym = scanner.scan(/\w+/)
34
- op = scanner.scan(/./)
35
- num = scanner.scan(/\d+/)
36
- return @_current_node.send(sym.to_sym).send(op.to_sym, num.to_i)
37
- end
38
- if t = scanner.scan(/\['[a-zA-Z@&\*\/\$%\^\?_]+'\]+/)
39
- elements << t.gsub(/\[|\]|'|\s+/, '')
40
- elsif t = scanner.scan(/(\s+)?[<>=][=~]?(\s+)?/)
71
+ if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?]?/))
72
+ elements << t.gsub(/[\[\]'.]|\s+/, '')
73
+ elsif (t = scanner.scan(/(\s+)?[<>=!\-+][=~]?(\s+)?/))
41
74
  operator = t
42
- elsif t = scanner.scan(/(\s+)?'?.*'?(\s+)?/)
75
+ elsif (t = scanner.scan(/(\s+)?'?.*'?(\s+)?/))
43
76
  # If we encounter a node which does not contain `'` it means
44
77
  #  that we are dealing with a boolean type.
45
- operand = if t == 'true'
46
- true
47
- elsif t == 'false'
48
- false
49
- else
50
- operator.strip == '=~' ? t.to_regexp : t.delete("'").strip
51
- end
52
- elsif t = scanner.scan(/\/\w+\//)
53
- elsif t = scanner.scan(/.*/)
78
+ operand =
79
+ if t == 'true'
80
+ true
81
+ elsif t == 'false'
82
+ false
83
+ elsif operator.to_s.strip == '=~'
84
+ parse_regex(t)
85
+ else
86
+ t.gsub(%r{^'|'$}, '').strip
87
+ end
88
+ elsif (t = scanner.scan(/\/\w+\//))
89
+ elsif (t = scanner.scan(/.*/))
54
90
  raise "Could not process symbol: #{t}"
55
91
  end
56
92
  end
57
93
 
58
94
  el = if elements.empty?
59
95
  @_current_node
96
+ elsif @_current_node.is_a?(Hash)
97
+ dig(@_current_node, *elements)
60
98
  else
61
- dig(elements, @_current_node)
99
+ elements.inject(@_current_node, &:__send__)
62
100
  end
63
- return false if el.nil?
64
- return true if operator.nil? && el
101
+
102
+ return (el ? true : false) if el.nil? || operator.nil?
65
103
 
66
104
  el = Float(el) rescue el
67
105
  operand = Float(operand) rescue operand
@@ -71,13 +109,111 @@ class JsonPath
71
109
 
72
110
  private
73
111
 
74
- # @TODO: Remove this once JsonPath no longer supports ruby versions below 2.3
75
- def dig(keys, hash)
76
- return nil unless hash.is_a? Hash
77
- return nil unless hash.key?(keys.first)
78
- return hash.fetch(keys.first) if keys.size == 1
79
- prev = keys.shift
80
- dig(keys, hash.fetch(prev))
112
+ # /foo/i -> Regex.new("foo", Regexp::IGNORECASE) without using eval
113
+ # also supports %r{foo}i
114
+ # following https://github.com/seamusabshere/to_regexp/blob/master/lib/to_regexp.rb
115
+ def parse_regex(t)
116
+ t =~ REGEX
117
+ content = $1 || $3
118
+ options = $2 || $4
119
+
120
+ raise ArgumentError, "unsupported regex #{t} use /foo/ style" if !content || !options
121
+
122
+ content = content.gsub '\\/', '/'
123
+
124
+ flags = 0
125
+ flags |= Regexp::IGNORECASE if options.include?('i')
126
+ flags |= Regexp::MULTILINE if options.include?('m')
127
+ flags |= Regexp::EXTENDED if options.include?('x')
128
+
129
+ # 'n' = none, 'e' = EUC, 's' = SJIS, 'u' = UTF-8
130
+ lang = options.scan(/[nes]/).join.downcase # ignores u since that is default and causes a warning
131
+
132
+ args = [content, flags]
133
+ args << lang unless lang.empty? # avoid warning
134
+ Regexp.new(*args)
135
+ end
136
+
137
+ #  This will break down a parenthesis from the left to the right
138
+ #  and replace the given expression with it's returned value.
139
+ # It does this in order to make it easy to eliminate groups
140
+ # one-by-one.
141
+ def parse_parentheses(str)
142
+ opening_index = 0
143
+ closing_index = 0
144
+
145
+ (0..str.length - 1).step(1) do |i|
146
+ opening_index = i if str[i] == '('
147
+ if str[i] == ')'
148
+ closing_index = i
149
+ break
150
+ end
151
+ end
152
+
153
+ to_parse = str[opening_index + 1..closing_index - 1]
154
+
155
+ #  handle cases like (true && true || false && true) in
156
+ # one giant parenthesis.
157
+ top = to_parse.split(/(&&)|(\|\|)/)
158
+ top = top.map(&:strip)
159
+ res = bool_or_exp(top.shift)
160
+ top.each_with_index do |item, index|
161
+ if item == '&&'
162
+ next_value = bool_or_exp(top[index + 1])
163
+ res &&= next_value
164
+ elsif item == '||'
165
+ next_value = bool_or_exp(top[index + 1])
166
+ res ||= next_value
167
+ end
168
+ end
169
+
170
+ #  if we are at the last item, the opening index will be 0
171
+ # and the closing index will be the last index. To avoid
172
+ # off-by-one errors we simply return the result at that point.
173
+ if closing_index + 1 >= str.length && opening_index == 0
174
+ res.to_s
175
+ else
176
+ "#{str[0..opening_index - 1]}#{res}#{str[closing_index + 1..str.length]}"
177
+ end
178
+ end
179
+
180
+ #  This is convoluted and I should probably refactor it somehow.
181
+ #  The map that is created will contain strings since essentially I'm
182
+ # constructing a string like `true || true && false`.
183
+ # With eval the need for this would disappear but never the less, here
184
+ #  it is. The fact is that the results can be either boolean, or a number
185
+ # in case there is only indexing happening like give me the 3rd item... or
186
+ # it also can be nil in case of regexes or things that aren't found.
187
+ # Hence, I have to be clever here to see what kind of variable I need to
188
+ # provide back.
189
+ def bool_or_exp(b)
190
+ if b.to_s == 'true'
191
+ return true
192
+ elsif b.to_s == 'false'
193
+ return false
194
+ elsif b.to_s == ''
195
+ return nil
196
+ end
197
+
198
+ b = Float(b) rescue b
199
+ b
200
+ end
201
+
202
+ # this simply makes sure that we aren't getting into the whole
203
+ #  parenthesis parsing business without knowing that every parenthesis
204
+ # has its pair.
205
+ def check_parenthesis_count(exp)
206
+ return true unless exp.include?('(')
207
+
208
+ depth = 0
209
+ exp.chars.each do |c|
210
+ if c == '('
211
+ depth += 1
212
+ elsif c == ')'
213
+ depth -= 1
214
+ end
215
+ end
216
+ depth == 0
81
217
  end
82
218
  end
83
219
  end
@@ -10,11 +10,11 @@ class JsonPath
10
10
  end
11
11
 
12
12
  def gsub(path, replacement = nil, &replacement_block)
13
- _gsub(_deep_copy, path, replacement ? proc { replacement } : replacement_block)
13
+ _gsub(_deep_copy, path, replacement ? proc(&method(:replacement)) : replacement_block)
14
14
  end
15
15
 
16
16
  def gsub!(path, replacement = nil, &replacement_block)
17
- _gsub(@obj, path, replacement ? proc { replacement } : replacement_block)
17
+ _gsub(@obj, path, replacement ? proc(&method(:replacement)) : replacement_block)
18
18
  end
19
19
 
20
20
  def delete(path = JsonPath::PATH_ALL)
@@ -46,9 +46,19 @@ class JsonPath
46
46
 
47
47
  def _delete(obj, path)
48
48
  JsonPath.new(path)[obj, :delete].each
49
+ obj = _remove(obj)
49
50
  Proxy.new(obj)
50
51
  end
51
52
 
53
+ def _remove(obj)
54
+ obj.each do |o|
55
+ if o.is_a?(Hash) || o.is_a?(Array)
56
+ _remove(o)
57
+ o.delete({})
58
+ end
59
+ end
60
+ end
61
+
52
62
  def _compact(obj, path)
53
63
  JsonPath.new(path)[obj, :compact].each
54
64
  Proxy.new(obj)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonPath
4
- VERSION = '0.9.3'
4
+ VERSION = '1.1.5'
5
5
  end
data/lib/jsonpath.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'strscan'
4
4
  require 'multi_json'
5
5
  require 'jsonpath/proxy'
6
+ require 'jsonpath/dig'
6
7
  require 'jsonpath/enumerable'
7
8
  require 'jsonpath/version'
8
9
  require 'jsonpath/parser'
@@ -11,30 +12,44 @@ require 'jsonpath/parser'
11
12
  # into a token array.
12
13
  class JsonPath
13
14
  PATH_ALL = '$..*'
15
+ MAX_NESTING_ALLOWED = 100
16
+
17
+ DEFAULT_OPTIONS = {
18
+ :default_path_leaf_to_null => false,
19
+ :symbolize_keys => false,
20
+ :use_symbols => false,
21
+ :allow_send => true,
22
+ :max_nesting => MAX_NESTING_ALLOWED
23
+ }
14
24
 
15
25
  attr_accessor :path
16
26
 
17
27
  def initialize(path, opts = {})
18
- @opts = opts
28
+ @opts = DEFAULT_OPTIONS.merge(opts)
29
+ set_max_nesting
19
30
  scanner = StringScanner.new(path.strip)
20
31
  @path = []
21
32
  until scanner.eos?
22
- if token = scanner.scan(/\$\B|@\B|\*|\.\./)
33
+ if (token = scanner.scan(/\$\B|@\B|\*|\.\./))
23
34
  @path << token
24
- elsif token = scanner.scan(/[\$@a-zA-Z0-9:_-]+/)
35
+ elsif (token = scanner.scan(/[$@\p{Alnum}:{}_ -]+/))
25
36
  @path << "['#{token}']"
26
- elsif token = scanner.scan(/'(.*?)'/)
37
+ elsif (token = scanner.scan(/'(.*?)'/))
27
38
  @path << "[#{token}]"
28
- elsif token = scanner.scan(/\[/)
39
+ elsif (token = scanner.scan(/\[/))
29
40
  @path << find_matching_brackets(token, scanner)
30
- elsif token = scanner.scan(/\]/)
41
+ elsif (token = scanner.scan(/\]/))
31
42
  raise ArgumentError, 'unmatched closing bracket'
43
+ elsif (token = scanner.scan(/\(.*\)/))
44
+ @path << token
32
45
  elsif scanner.scan(/\./)
33
46
  nil
34
- elsif token = scanner.scan(/[><=] \d+/)
47
+ elsif (token = scanner.scan(/[><=] \d+/))
35
48
  @path.last << token
36
- elsif token = scanner.scan(/./)
49
+ elsif (token = scanner.scan(/./))
37
50
  @path.last << token
51
+ else
52
+ raise ArgumentError, "character '#{scanner.peek(1)}' not supported in query"
38
53
  end
39
54
  end
40
55
  end
@@ -42,13 +57,13 @@ class JsonPath
42
57
  def find_matching_brackets(token, scanner)
43
58
  count = 1
44
59
  until count.zero?
45
- if t = scanner.scan(/\[/)
60
+ if (t = scanner.scan(/\[/))
46
61
  token << t
47
62
  count += 1
48
- elsif t = scanner.scan(/\]/)
63
+ elsif (t = scanner.scan(/\]/))
49
64
  token << t
50
65
  count -= 1
51
- elsif t = scanner.scan(/[^\[\]]+/)
66
+ elsif (t = scanner.scan(/[^\[\]]+/))
52
67
  token << t
53
68
  elsif scanner.eos?
54
69
  raise ArgumentError, 'unclosed bracket'
@@ -63,8 +78,47 @@ class JsonPath
63
78
  res
64
79
  end
65
80
 
66
- def on(obj_or_str)
67
- enum_on(obj_or_str).to_a
81
+ def on(obj_or_str, opts = {})
82
+ a = enum_on(obj_or_str).to_a
83
+ if symbolize_keys?(opts)
84
+ a.map! do |e|
85
+ e.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v; }
86
+ end
87
+ end
88
+ a
89
+ end
90
+
91
+ def self.fetch_all_path(obj)
92
+ all_paths = ['$']
93
+ find_path(obj, '$', all_paths, obj.class == Array)
94
+ return all_paths
95
+ end
96
+
97
+ def self.find_path(obj, root_key, all_paths, is_array = false)
98
+ obj.each do |key, value|
99
+ table_params = { key: key, root_key: root_key}
100
+ is_loop = value.class == Array || value.class == Hash
101
+ if is_loop
102
+ path_exp = construct_path(table_params)
103
+ all_paths << path_exp
104
+ find_path(value, path_exp, all_paths, value.class == Array)
105
+ elsif is_array
106
+ table_params[:index] = obj.find_index(key)
107
+ path_exp = construct_path(table_params)
108
+ find_path(key, path_exp, all_paths, key.class == Array) if key.class == Hash || key.class == Array
109
+ all_paths << path_exp
110
+ else
111
+ all_paths << construct_path(table_params)
112
+ end
113
+ end
114
+ end
115
+
116
+ def self.construct_path(table_row)
117
+ if table_row[:index]
118
+ return table_row[:root_key] + '['+ table_row[:index].to_s + ']'
119
+ else
120
+ return table_row[:root_key] + '.'+ table_row[:key]
121
+ end
68
122
  end
69
123
 
70
124
  def first(obj_or_str, *args)
@@ -72,7 +126,7 @@ class JsonPath
72
126
  end
73
127
 
74
128
  def enum_on(obj_or_str, mode = nil)
75
- JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
129
+ JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str, @opts), mode,
76
130
  @opts)
77
131
  end
78
132
  alias [] enum_on
@@ -87,11 +141,20 @@ class JsonPath
87
141
 
88
142
  private
89
143
 
90
- def self.process_object(obj_or_str)
91
- obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str
144
+ def self.process_object(obj_or_str, opts = {})
145
+ obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str, max_nesting: opts[:max_nesting]) : obj_or_str
92
146
  end
93
147
 
94
148
  def deep_clone
95
149
  Marshal.load Marshal.dump(self)
96
150
  end
151
+
152
+ def set_max_nesting
153
+ return unless @opts[:max_nesting].is_a?(Integer) && @opts[:max_nesting] > MAX_NESTING_ALLOWED
154
+ @opts[:max_nesting] = false
155
+ end
156
+
157
+ def symbolize_keys?(opts)
158
+ opts.fetch(:symbolize_keys, @opts&.dig(:symbolize_keys))
159
+ end
97
160
  end