Package tlib :: Package base :: Module JsonHelper
[hide private]
[frames] | no frames]

Source Code for Module tlib.base.JsonHelper

  1  import re 
  2  import difflib 
  3  # noinspection PyPackageRequirements 
  4  from datadiff.tools import assert_equals 
  5  import json 
  6  # noinspection PyPackageRequirements 
  7  import pytest 
  8  from collections import OrderedDict 
  9  from warnings import warn 
 10  from tlib.base.TestHelper import sort_list, sort_dict 
11 12 13 # noinspection PyUnresolvedReferences 14 -class JsonHelper(object):
15 """ 16 Class to work with JSON data 17 """ 18 19 @staticmethod
20 - def assert_json_equals(json1, json2, ignore_order=True, exclude_keys=list()):
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
41 - def _validate_json_ignoring_order(json1, json2, exclude_keys):
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 #If it's a string, convert to dictionary 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 #make sure any lists inside the JSON value are sorted, so diff will not fail 61 if type(item) is dict: 62 #Sort items inside lists 63 item = sort_dict(item) 64 elif type(item) is list: 65 #Sort items inside lists 66 item = sort_list(item) 67 else: 68 pytest.fail("Parameter doesn't represent a valid JSON object: %s" % item) 69 70 #Delete unwanted keys 71 JsonHelper.remove_keys(item, exclude_keys) 72 73 items[i] = item 74 75 assert_equals(items[0], items[1])
76 77 @staticmethod
78 - def _validate_json_with_order(json1, json2, exclude_keys=list()):
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 # By default json.loads doesn't care about key order. Let's provide 103 # an object_pairs_hook function to ensure key order is kept 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 #Delete unwanted keys 110 JsonHelper.remove_keys(item, exclude_keys) 111 112 items[i] = item 113 114 #Do validation 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
125 - def remove_keys(data, keys, top_level=True):
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
139 - def _remove_key(data, key, top_level=True):
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 global orig_key 149 150 #Parameter validation 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 #Create a copy of the key object so we don't modify the original 159 orig_key = list(key) 160 161 #Check if first element in the keys has syntax '*' and change it to be able to match the right values 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 # split indexed items in two, only first time function is called 170 # eg. ["node1", "node[2]"] => ["node1", "node", 2] 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 #check if next key is an index, otherwise fail 189 if type(key[0]) is int: 190 index = key.pop(0) 191 192 if len(key) == 0: 193 #Found the key 194 data.pop(index) 195 else: 196 #Still need to find children nodes 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 #Validate 203 for k in data: 204 if k == key[0]: 205 key.pop(0) 206 207 if len(key) == 0: 208 #Found the key 209 del(data[k]) 210 else: 211 #Still need to find children nodes 212 JsonHelper._remove_key(data[k], key, top_level=False) 213 214 # Don't need to continue iterating. Node was already found 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 # Not all keys were found. Can't fail here because it would make impossible testing the scenario where 221 # a JSON payload has a key and other doesn't 222 warn("Key '%s' does not represent a valid element" % '.'.join(orig_key)) 223 224 if top_level: 225 #Restore key variable 226 # noinspection PyUnusedLocal 227 key = list(orig_key)
228