1 """This module contains single a function for normalizing JSON strings."""
2 # Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 # This program and the accompanying materials are made available under the
5 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 # and is available at http://www.eclipse.org/legal/epl-v10.html
8 import collections as _collections
12 import simplejson as _json
13 except ImportError: # Python2.7 calls it json.
17 __author__ = "Vratko Polak"
18 __copyright__ = "Copyright(c) 2015-2016, Cisco Systems, Inc."
19 __license__ = "Eclipse Public License v1.0"
20 __email__ = "vrpolak@cisco.com"
23 # Internal details; look down below for Robot Keywords.
28 Hashable sorted frozen list implementation stub.
30 Supports only __init__, __repr__ and __hash__ methods.
31 Other list methods are available, but they may break contract.
34 def __init__(self, *args, **kwargs):
35 """Contruct super, sort and compute repr and hash cache values."""
36 sup = super(_Hsfl, self)
37 sup.__init__(*args, **kwargs)
39 self.__repr = repr(tuple(self))
40 self.__hash = hash(self.__repr)
43 """Return cached repr string."""
47 """Return cached hash."""
51 class _Hsfod(_collections.OrderedDict):
53 Hashable sorted (by key) frozen OrderedDict implementation stub.
55 Supports only __init__, __repr__ and __hash__ methods.
56 Other OrderedDict methods are available, but they may break contract.
59 def __init__(self, *args, **kwargs):
60 """Put arguments to OrderedDict, sort, pass to super, cache values."""
61 self_unsorted = _collections.OrderedDict(*args, **kwargs)
62 items_sorted = sorted(list(self_unsorted.items()), key=repr)
63 sup = super(_Hsfod, self) # possibly something else than OrderedDict
64 sup.__init__(items_sorted)
65 # Repr string is used for sorting, keys are more important than values.
67 "{" + repr(list(self.keys())) + ":" + repr(list(self.values())) + "}"
69 self.__hash = hash(self.__repr)
72 """Return cached repr string."""
76 """Return cached hash."""
80 def _hsfl_array(s_and_end, scan_once, **kwargs):
81 """Scan JSON array as usual, but return hsfl instead of list."""
82 values, end = _json.decoder.JSONArray(s_and_end, scan_once, **kwargs)
83 return _Hsfl(values), end
86 class _Decoder(_json.JSONDecoder):
87 """Private class to act as customized JSON decoder.
89 Based on: http://stackoverflow.com/questions/10885238/
90 python-change-list-type-for-json-decoding"""
92 def __init__(self, **kwargs):
93 """Initialize decoder with special array implementation."""
94 _json.JSONDecoder.__init__(self, **kwargs)
95 # Use the custom JSONArray
96 self.parse_array = _hsfl_array
97 # Use the python implemenation of the scanner
98 self.scan_once = _json.scanner.py_make_scanner(self)
101 # Robot Keywords; look above for internal details.
104 def loads_sorted(text, strict=False):
105 """Return Python object with sorted arrays and dictionary keys."""
106 object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
107 return object_decoded
110 def dumps_indented(obj, indent=1):
112 Wrapper for json.dumps with default indentation level.
114 The main value is that BuiltIn.Evaluate cannot easily accept Python object
115 as part of its argument.
116 Also, allows to use something different from RequestsLibrary.To_Json
119 pretty_json = _json.dumps(obj, separators=(",", ": "), indent=indent)
120 return pretty_json + "\n" # to avoid diff "no newline" warning line
123 def sort_bits(obj, keys_with_bits=[]):
125 Rearrange string values of list bits names in alphabetical order.
127 This function looks at dict items with known keys.
128 If the value is string, space-separated names are sorted.
129 This function is recursive over dicts and lists.
130 Current implementation performs re-arranging in-place (to save memory),
131 so it is not required to store the return value.
133 The intended usage is for composite objects which contain
134 OrderedDict elements. The implementation makes sure that ordering
135 (dictated by keys) is preserved. Support for generic dicts is an added value.
137 Sadly, dict (at least in Python 2.7) does not have __updatevalue__(key) method
138 which would guarantee iteritems() is not affected when value is updated.
139 Current "obj[key] = value" implementation is safe for dict and OrderedDict,
140 but it may be not safe for other subclasses of dict.
142 TODO: Should this docstring include links to support dict and OrderedDict safety?
144 if isinstance(obj, dict):
145 for key, value in obj.items():
146 # Unicode is not str and vice versa, isinstance has to check for both.
147 # Luckily, "in" recognizes equivalent strings in different encodings.
148 # Type "bytes" is added for Python 3 compatibility.
149 if key in keys_with_bits and isinstance(value, (str, bytes)):
150 obj[key] = " ".join(sorted(value.split(" ")))
152 sort_bits(value, keys_with_bits)
153 # A string is not a list, so there is no risk of recursion over characters.
154 elif isinstance(obj, list):
156 sort_bits(item, keys_with_bits)
160 def hide_volatile(obj, keys_with_volatiles=[]):
162 Takes list of keys with volatile values, and replaces them with generic "*"
164 :param obj: python dict from json
165 :param keys_with_volatiles: list of volatile keys
168 if isinstance(obj, dict):
169 for key, value in obj.items():
170 # Unicode is not str and vice versa, isinstance has to check for both.
171 # Luckily, "in" recognizes equivalent strings in different encodings.
172 # Type "bytes" is added for Python 3 compatibility.
173 if key in keys_with_volatiles and isinstance(
174 value, (str, bytes, int, bool)
178 hide_volatile(value, keys_with_volatiles)
179 # A string is not a list, so there is no risk of recursion over characters.
180 elif isinstance(obj, list):
182 hide_volatile(item, keys_with_volatiles)
186 def normalize_json_text(
191 keys_with_volatiles=[],
195 Attempt to return sorted indented JSON string.
197 If jmes_path is set the related subset of JSON data is returned as
198 indented JSON string if the subset exists. Empty string is returned if the
199 subset doesn't exist.
200 Empty string is returned if text is not passed.
201 If parse error happens:
202 If strict is true, raise the exception.
203 If strict is not true, return original text with error message.
204 If keys_with_bits is non-empty, run sort_bits on intermediate Python object.
211 json_obj = _json.loads(text)
212 subset = jmespath.search(jmes_path, json_obj)
215 text = _json.dumps(subset)
218 object_decoded = loads_sorted(text)
219 except ValueError as err:
223 return str(err) + "\n" + text
225 sort_bits(object_decoded, keys_with_bits)
226 if keys_with_volatiles:
227 hide_volatile(object_decoded, keys_with_volatiles)
229 pretty_json = dumps_indented(object_decoded, indent=indent)