Skip to content

Commit c6d38cf

Browse files
committed
duck-type values responding to #to_hash or #to_ary rather than checking class
1 parent 1844ca4 commit c6d38cf

File tree

6 files changed

+69
-54
lines changed

6 files changed

+69
-54
lines changed

lib/jmespath/nodes.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ class Node
55
def visit(value)
66
end
77

8-
def hash_like?(value)
9-
Hash === value || Struct === value
10-
end
11-
128
def optimize
139
self
1410
end

lib/jmespath/nodes/field.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ def initialize(key)
88
end
99

1010
def visit(value)
11-
if value.is_a?(Array) && @key.is_a?(Integer)
12-
value[@key]
13-
elsif value.is_a?(Hash)
11+
if value.respond_to?(:to_ary) && @key.is_a?(Integer)
12+
value.to_ary[@key]
13+
elsif value.respond_to?(:to_hash)
14+
value = value.to_hash
1415
if !(v = value[@key]).nil?
1516
v
1617
elsif @key_sym && !(v = value[@key_sym]).nil?
@@ -48,9 +49,10 @@ def initialize(keys)
4849

4950
def visit(obj)
5051
@keys.reduce(obj) do |value, key|
51-
if value.is_a?(Array) && key.is_a?(Integer)
52-
value[key]
53-
elsif value.is_a?(Hash)
52+
if value.respond_to?(:to_ary) && key.is_a?(Integer)
53+
value.to_ary[key]
54+
elsif value.respond_to?(:to_hash)
55+
value = value.to_hash
5456
if !(v = value[key]).nil?
5557
v
5658
elsif (sym = @key_syms[key]) && !(v = value[sym]).nil?

lib/jmespath/nodes/flatten.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ def initialize(child)
88

99
def visit(value)
1010
value = @child.visit(value)
11-
if Array === value
12-
value.each_with_object([]) do |v, values|
13-
if Array === v
14-
values.concat(v)
11+
if value.respond_to?(:to_ary)
12+
value.to_ary.each_with_object([]) do |v, values|
13+
if v.respond_to?(:to_ary)
14+
values.concat(v.to_ary)
1515
else
1616
values.push(v)
1717
end

lib/jmespath/nodes/function.rb

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ def call(args)
5050

5151
module TypeChecker
5252
def get_type(value)
53-
case value
54-
when String then STRING_TYPE
55-
when true, false then BOOLEAN_TYPE
56-
when nil then NULL_TYPE
57-
when Numeric then NUMBER_TYPE
58-
when Hash, Struct then OBJECT_TYPE
59-
when Array then ARRAY_TYPE
60-
when Expression then EXPRESSION_TYPE
53+
if value.respond_to?(:to_str)
54+
STRING_TYPE
55+
elsif value == true || value == false
56+
BOOLEAN_TYPE
57+
elsif value == nil
58+
NULL_TYPE
59+
elsif value.is_a?(Numeric)
60+
NUMBER_TYPE
61+
elsif value.respond_to?(:to_hash) || value.is_a?(Struct)
62+
OBJECT_TYPE
63+
elsif value.respond_to?(:to_ary)
64+
ARRAY_TYPE
65+
elsif value.is_a?(Expression)
66+
EXPRESSION_TYPE
6167
end
6268
end
6369

@@ -106,7 +112,8 @@ def call(args)
106112
else
107113
return maybe_raise Errors::InvalidArityError, "function avg() expects one argument"
108114
end
109-
if Array === values
115+
if values.respond_to?(:to_ary)
116+
values = values.to_ary
110117
return nil if values.empty?
111118
values.inject(0) do |total,n|
112119
if Numeric === n
@@ -145,8 +152,10 @@ def call(args)
145152
if args.count == 2
146153
haystack = args[0]
147154
needle = args[1]
148-
if String === haystack || Array === haystack
149-
haystack.include?(needle)
155+
if haystack.respond_to?(:to_str)
156+
haystack.to_str.include?(needle)
157+
elsif haystack.respond_to?(:to_ary)
158+
haystack.to_ary.include?(needle)
150159
else
151160
return maybe_raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
152161
end
@@ -182,9 +191,10 @@ def call(args)
182191
else
183192
return maybe_raise Errors::InvalidArityError, "function length() expects one argument"
184193
end
185-
case value
186-
when Hash, Array, String then value.size
187-
else return maybe_raise Errors::InvalidTypeError, "function length() expects string, array or object"
194+
if value.respond_to?(:to_hash) || value.respond_to?(:to_ary) || value.respond_to?(:to_str)
195+
value.size
196+
else
197+
return maybe_raise Errors::InvalidTypeError, "function length() expects string, array or object"
188198
end
189199
end
190200
end
@@ -202,8 +212,8 @@ def call(args)
202212
else
203213
return maybe_raise Errors::InvalidTypeError, "function map() expects the first argument to be an expression"
204214
end
205-
if Array === args[1]
206-
list = args[1]
215+
if args[1].respond_to?(:to_ary)
216+
list = args[1].to_ary
207217
else
208218
return maybe_raise Errors::InvalidTypeError, "function map() expects the second argument to be an list"
209219
end
@@ -223,7 +233,8 @@ def call(args)
223233
else
224234
return maybe_raise Errors::InvalidArityError, "function max() expects one argument"
225235
end
226-
if Array === values
236+
if values.respond_to?(:to_ary)
237+
values = values.to_ary
227238
return nil if values.empty?
228239
first = values.first
229240
first_type = get_type(first)
@@ -258,7 +269,8 @@ def call(args)
258269
else
259270
return maybe_raise Errors::InvalidArityError, "function min() expects one argument"
260271
end
261-
if Array === values
272+
if values.respond_to?(:to_ary)
273+
values = values.to_ary
262274
return nil if values.empty?
263275
first = values.first
264276
first_type = get_type(first)
@@ -302,12 +314,10 @@ class KeysFunction < Function
302314
def call(args)
303315
if args.count == 1
304316
value = args.first
305-
if hash_like?(value)
306-
case value
307-
when Hash then value.keys.map(&:to_s)
308-
when Struct then value.members.map(&:to_s)
309-
else raise NotImplementedError
310-
end
317+
if value.respond_to?(:to_hash)
318+
value.to_hash.keys.map(&:to_s)
319+
elsif value.is_a?(Struct)
320+
value.members.map(&:to_s)
311321
else
312322
return maybe_raise Errors::InvalidTypeError, "function keys() expects a hash"
313323
end
@@ -323,10 +333,12 @@ class ValuesFunction < Function
323333
def call(args)
324334
if args.count == 1
325335
value = args.first
326-
if hash_like?(value)
327-
value.values
328-
elsif Array === value
329-
value
336+
if value.respond_to?(:to_hash)
337+
value.to_hash.values
338+
elsif value.is_a?(Struct)
339+
value.to_h.values
340+
elsif value.respond_to?(:to_ary)
341+
value.to_ary
330342
else
331343
return maybe_raise Errors::InvalidTypeError, "function values() expects an array or a hash"
332344
end
@@ -345,8 +357,8 @@ def call(args)
345357
values = args[1]
346358
if !(String === glue)
347359
return maybe_raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
348-
elsif Array === values && values.all? { |v| String === v }
349-
values.join(glue)
360+
elsif values.respond_to?(:to_ary) && values.to_ary.all? { |v| String === v }
361+
values.to_ary.join(glue)
350362
else
351363
return maybe_raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
352364
end
@@ -390,8 +402,8 @@ class SumFunction < Function
390402
FUNCTIONS['sum'] = self
391403

392404
def call(args)
393-
if args.count == 1 && Array === args.first
394-
args.first.inject(0) do |sum,n|
405+
if args.count == 1 && args.first.respond_to?(:to_ary)
406+
args.first.to_ary.inject(0) do |sum,n|
395407
if Numeric === n
396408
sum + n
397409
else
@@ -424,7 +436,8 @@ class SortFunction < Function
424436
def call(args)
425437
if args.count == 1
426438
value = args.first
427-
if Array === value
439+
if value.respond_to?(:to_ary)
440+
value = value.to_ary
428441
# every element in the list must be of the same type
429442
array_type = get_type(value[0])
430443
if array_type == STRING_TYPE || array_type == NUMBER_TYPE || value.size == 0
@@ -616,7 +629,9 @@ def call(args)
616629
return maybe_raise Errors::InvalidArityError, msg
617630
end
618631
value = args.first
619-
if Array === value || String === value
632+
if value.respond_to?(:to_ary)
633+
value.to_ary.reverse
634+
elsif String === value
620635
value.reverse
621636
else
622637
msg = "function reverse() expects an array or string"
@@ -630,7 +645,7 @@ class ToArrayFunction < Function
630645

631646
def call(args)
632647
value = args.first
633-
Array === value ? value : [value]
648+
value.respond_to?(:to_ary) ? value.to_ary : [value]
634649
end
635650
end
636651
end

lib/jmespath/nodes/projection.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def visit(value)
4545

4646
class ArrayProjection < Projection
4747
def extract_targets(target)
48-
if Array === target
49-
target
48+
if target.respond_to?(:to_ary)
49+
target.to_ary
5050
else
5151
nil
5252
end
@@ -63,8 +63,10 @@ class FastArrayProjection < ArrayProjection
6363

6464
class ObjectProjection < Projection
6565
def extract_targets(target)
66-
if hash_like?(target)
67-
target.values
66+
if target.respond_to?(:to_hash)
67+
target.to_hash.values
68+
elsif target.is_a?(Struct)
69+
target.to_h.values
6870
else
6971
nil
7072
end

lib/jmespath/nodes/slice.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def initialize(start, stop, step)
1010
end
1111

1212
def visit(value)
13-
if String === value || Array === value
13+
if (value = value.respond_to?(:to_str) ? value.to_str : value.respond_to?(:to_ary) ? value.to_ary : nil)
1414
start, stop, step = adjust_slice(value.size, @start, @stop, @step)
1515
result = []
1616
if step > 0

0 commit comments

Comments
 (0)