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