1
1
# This is a Python implementation of the following jsonLogic JS library:
2
2
# https://github.com/jwadhams/json-logic-js
3
+ from __future__ import unicode_literals
3
4
4
5
import sys
5
6
from six .moves import reduce
6
7
import logging
7
8
8
9
logger = logging .getLogger (__name__ )
9
10
11
+ try :
12
+ unicode
13
+ except NameError :
14
+ pass
15
+ else :
16
+ # Python 2 fallback.
17
+ str = unicode
18
+
10
19
11
20
def if_ (* args ):
12
21
"""Implements the 'if' operator with support for multiple elseif-s."""
13
- assert len (args ) >= 2
14
22
for i in range (0 , len (args ) - 1 , 2 ):
15
23
if args [i ]:
16
24
return args [i + 1 ]
@@ -29,6 +37,56 @@ def soft_equals(a, b):
29
37
return a == b
30
38
31
39
40
+ def hard_equals (a , b ):
41
+ """Implements the '===' operator."""
42
+ if type (a ) != type (b ):
43
+ return False
44
+ return a == b
45
+
46
+
47
+ def less (a , b , * args ):
48
+ """Implements the '<' operator with JS-style type coertion."""
49
+ types = set ([type (a ), type (b )])
50
+ if float in types or int in types :
51
+ try :
52
+ a , b = float (a ), float (b )
53
+ except TypeError :
54
+ # NaN
55
+ return False
56
+ return a < b and (not args or less (b , * args ))
57
+
58
+
59
+ def less_or_equal (a , b , * args ):
60
+ """Implements the '<=' operator with JS-style type coertion."""
61
+ return (
62
+ less (a , b ) or soft_equals (a , b )
63
+ ) and (not args or less_or_equal (b , * args ))
64
+
65
+
66
+ def to_numeric (arg ):
67
+ """
68
+ Converts a string either to int or to float.
69
+ This is important, because e.g. {"!==": [{"+": "0"}, 0.0]}
70
+ """
71
+ if isinstance (arg , str ):
72
+ if '.' in arg :
73
+ return float (arg )
74
+ else :
75
+ return int (arg )
76
+ return arg
77
+
78
+ def plus (* args ):
79
+ """Sum converts either to ints or to floats."""
80
+ return sum (to_numeric (arg ) for arg in args )
81
+
82
+
83
+ def minus (* args ):
84
+ """Also, converts either to ints or to floats."""
85
+ if len (args ) == 1 :
86
+ return - to_numeric (args [0 ])
87
+ return to_numeric (args [0 ]) - to_numeric (args [1 ])
88
+
89
+
32
90
def merge (* args ):
33
91
"""Implements the 'merge' operator for merging lists."""
34
92
ret = []
@@ -57,6 +115,8 @@ def get_var(data, var_name, not_found=None):
57
115
def missing (data , * args ):
58
116
"""Implements the missing operator for finding missing variables."""
59
117
not_found = object ()
118
+ if args and isinstance (args [0 ], list ):
119
+ args = args [0 ]
60
120
ret = []
61
121
for arg in args :
62
122
if get_var (data , arg , not_found ) is not_found :
@@ -83,28 +143,29 @@ def missing_some(data, min_required, args):
83
143
84
144
operations = {
85
145
"==" : soft_equals ,
86
- "===" : lambda a , b : a is b ,
146
+ "===" : hard_equals ,
87
147
"!=" : lambda a , b : not soft_equals (a , b ),
88
- "!==" : lambda a , b : a is not b ,
89
- ">" : lambda a , b : a > b ,
90
- ">=" : lambda a , b : a >= b ,
91
- "<" : lambda a , b , c = None : a < b if c is None else a < b < c ,
92
- "<=" : lambda a , b , c = None : a <= b if c is None else a <= b <= c ,
148
+ "!==" : lambda a , b : not hard_equals ( a , b ) ,
149
+ ">" : lambda a , b : less ( b , a ) ,
150
+ ">=" : lambda a , b : less ( b , a ) or soft_equals ( a , b ) ,
151
+ "<" : less ,
152
+ "<=" : less_or_equal ,
93
153
"!" : lambda a : not a ,
154
+ "!!" : bool ,
94
155
"%" : lambda a , b : a % b ,
95
156
"and" : lambda * args : reduce (lambda total , arg : total and arg , args , True ),
96
157
"or" : lambda * args : reduce (lambda total , arg : total or arg , args , False ),
97
158
"?:" : lambda a , b , c : b if a else c ,
98
159
"if" : if_ ,
99
160
"log" : lambda a : logger .info (a ) or a ,
100
161
"in" : lambda a , b : a in b if "__contains__" in dir (b ) else False ,
101
- "cat" : lambda * args : "" .join (args ),
102
- "+" : lambda * args : sum ( float ( arg ) for arg in args ) ,
162
+ "cat" : lambda * args : "" .join (str ( arg ) for arg in args ),
163
+ "+" : plus ,
103
164
"*" : lambda * args : reduce (lambda total , arg : total * float (arg ), args , 1 ),
104
- "-" : lambda a , b = None : - a if b is None else a - b ,
165
+ "-" : minus ,
105
166
"/" : lambda a , b = None : a if b is None else float (a ) / float (b ),
106
- "min" : min ,
107
- "max" : max ,
167
+ "min" : lambda * args : min ( args ) ,
168
+ "max" : lambda * args : max ( args ) ,
108
169
"merge" : merge ,
109
170
"count" : lambda * args : sum (1 if a else 0 for a in args ),
110
171
}
@@ -124,14 +185,10 @@ def jsonLogic(tests, data=None):
124
185
# Easy syntax for unary operators, like {"var": "x"} instead of strict
125
186
# {"var": ["x"]}
126
187
if not isinstance (values , list ) and not isinstance (values , tuple ):
127
- values = jsonLogic (values , data )
128
- # Let's do recursion first. If it's still not a list after processing,
129
- # then it means it's unary syntax sugar.
130
- if not isinstance (values , list ) and not isinstance (values , tuple ):
131
- values = [values ]
132
- else :
133
- # Recursion!
134
- values = [jsonLogic (val , data ) for val in values ]
188
+ values = [values ]
189
+
190
+ # Recursion!
191
+ values = [jsonLogic (val , data ) for val in values ]
135
192
136
193
if operator == 'var' :
137
194
return get_var (data , * values )
0 commit comments