1 import re
2 import difflib
3
4 from datadiff.tools import assert_equals
5 import json
6
7 import pytest
8 from collections import OrderedDict
9 from warnings import warn
10 from tlib.base.TestHelper import sort_list, sort_dict
11 import jsonpath_rw
12 from tlib.base.ExceptionHelper import BadJsonFormatError
16 """
17 Functions for modifying and validating JSON data
18 """
19
20 @staticmethod
22 """
23 Compares two JSON values\n
24 If parameter validate_order is True, position of keys is taken into account during validation\n
25 If json1 or json2 are strings, it must be a valid JSON string
26
27 @param json1: String or dictionary to compare
28 @type json1: str, dict
29 @param json2: String or dictionary to compare
30 @type json2: str, dict
31 @param ignore_order: If true, order of keys is not validated
32 @type ignore_order: bool
33 @param exclude_keys: Keys to exclude from comparison, in the format 'key2.key3[2].key1'
34 @type exclude_keys: list
35 @raise AssertionError: JSON values doesn't match
36 """
37 if ignore_order:
38 JsonHelper._validate_json_ignoring_order(json1, json2, exclude_keys)
39 else:
40 JsonHelper._validate_json_with_order(json1, json2, exclude_keys)
41
42 @staticmethod
44 """
45 Compares two JSON values, ignoring position of keys\n
46 If json1 or json2 are strings, it must be a valid JSON string
47 @param json1: String or dictionary to compare
48 @type json1: str, dict
49 @param json2: String or dictionary to compare
50 @type json2: str, dict
51 @raise AssertionError: JSON values doesn't match
52 """
53 items = [json1, json2]
54 for i, item in enumerate(items):
55
56 if isinstance(item, basestring):
57 try:
58 item = json.loads(item)
59 except ValueError:
60 pytest.fail("Value doesn't represent a valid JSON string\n%s" % item)
61
62
63 if type(item) is dict:
64
65 item = sort_dict(item)
66 elif type(item) is list:
67
68 item = sort_list(item)
69 else:
70 pytest.fail("Parameter doesn't represent a valid JSON object: %s" % item)
71
72
73 JsonHelper.remove_keys(item, exclude_keys)
74
75 items[i] = item
76
77 assert_equals(items[0], items[1])
78
79 @staticmethod
81 """
82 Compares two JSON values, taking into account key order
83 If json1 or json2 are strings, it must be a valid JSON string
84 @param json1: String to compare
85 @type json1: str
86 @param json2: String to compare
87 @type json2: str
88 @param exclude_keys: Keys to exclude from comparison, in the format 'key2.key3[2].key1'
89 @type exclude_keys: list
90 @raise AssertionError: JSON values doesn't match
91 """
92
93 def object_pairs_hook(values):
94 """
95 Method that stores objects in an OrderedDict so comparison takes into account key order
96 """
97 return OrderedDict(values)
98
99 items = [json1, json2]
100 for i, item in enumerate(items):
101 if not isinstance(item, basestring):
102 pytest.fail("Only strings are allowed when validating key order")
103
104
105
106 try:
107 item = json.loads(item, object_pairs_hook=object_pairs_hook)
108 except ValueError:
109 pytest.fail("Value doesn't represent a valid JSON string\n%s" % item)
110
111
112 JsonHelper.remove_keys(item, exclude_keys)
113
114 items[i] = item
115
116
117 if items[0] != items[1]:
118 json1 = json.dumps(items[0], indent=3).splitlines(True)
119 json2 = json.dumps(items[1], indent=3).splitlines(True)
120 diff = difflib.unified_diff(json1, json2)
121 diff_txt = "".join(diff)
122
123 raise AssertionError(r"JSON values didn't match\nValue1:\n%s\n\nValue2:\n%s\n\nDiff:\n%s""" %
124 ("".join(json1), "".join(json2), diff_txt))
125
126 @staticmethod
128 """
129 Takes a dictionary or a list and removes keys specified by 'keys' parameter
130 @param data: dictionary or OrderedDict that will be modified.
131 Object is replaced in place
132 @type data: dictionary, OrderedDict
133 @param keys: array indicating the path to remove.
134 e.g. ['businessHours.headings[2]', 'businessHours.values.name']
135 @type keys: list
136 """
137 for key in keys:
138 JsonHelper._remove_key(data, key.split('.'), top_level)
139
140 @staticmethod
142 """
143 Takes a dictionary or a list and removes keys specified by 'keys' parameter
144 @param data: dictionary or OrderedDict that will be modified.
145 Object is replaced in place
146 @type data: dictionary, OrderedDict
147 @param key: array indicating the path to remove. e.g. ['businessHours', 'headings[2]']
148 @type key: list
149 """
150 orig_key = None
151
152
153 if type(key) is not list:
154 pytest.fail("Invalid argument key. Was expecting a list")
155
156 if not (type(data) in (dict, list) or isinstance(data, OrderedDict)):
157 pytest.fail("Invalid argument data. Was expecting a dict or OrderedDict")
158
159 if top_level:
160
161 orig_key = list(key)
162
163
164 match = re.search("^\*\[(.*)\]$", key[0])
165 if match:
166 try:
167 key[0] = int(match.group(1))
168 except ValueError:
169 pytest.fail("Index '%s' is not a valid integer" % match.group(2))
170
171
172
173 new_key = list()
174
175 for i, value in enumerate(key):
176 match = re.search("(^.+)\[(.+)\]$", str(value))
177 if match:
178 try:
179 new_key.append(match.group(1))
180 new_key.append(int(match.group(2)))
181 except ValueError:
182 pytest.fail("Index '%s' is not a valid integer" % match.group(2))
183
184 else:
185 new_key.append(value)
186
187 key = list(new_key)
188
189 if type(data) is list:
190
191 if type(key[0]) is int:
192 index = key.pop(0)
193
194 if len(key) == 0:
195
196 data.pop(index)
197 else:
198
199 JsonHelper._remove_key(data[index], key, top_level=False)
200
201 else:
202 pytest.fail("Key '%s' is not valid for the given JSON object" % key)
203 elif type(data) is dict or isinstance(data, OrderedDict):
204
205 for k in data:
206 if k == key[0]:
207 key.pop(0)
208
209 if len(key) == 0:
210
211 del(data[k])
212 else:
213
214 JsonHelper._remove_key(data[k], key, top_level=False)
215
216
217 break
218 else:
219 pytest.fail("Element %s is not allowed in a JSON response" % type(data))
220
221 if len(key) > 0:
222
223
224 warn("Key '%s' does not represent a valid element" % '.'.join(orig_key))
225
226 if top_level:
227
228
229 key = list(orig_key)
230
231 @staticmethod
233 """"
234 Applies a JSON path to a JSON string and returns the resulting node
235 @param data:
236 @type data: str, dict
237 """
238
239 if isinstance(data, basestring):
240 try:
241 json_data = json.loads(data)
242 except ValueError:
243 pytest.fail("Value doesn't represent a valid JSON string\n%s" % item)
244 elif type(data) is dict:
245
246 json_data = data
247 else:
248 raise BadJsonFormatError("Parameter doesn't represent a valid JSON object: %s" % item)
249
250 jsonpath_expr = jsonpath_rw.parse(json_path)
251 result = []
252 for i in jsonpath_expr.find(json_data):
253 result.append(i.value)
254
255 return result
256