Source code for viscid.vjson

"""Wrap python's json parser enabling comments and trailing commas

Comments are either # or //
"""
from __future__ import print_function

import sys

try:
    import json as _json
except ImportError:
    # If python version is 2.5 or less, use simplejson
    import simplejson as _json  # pylint: disable=import-error

[docs]def find_all(s, substrings, start=0, end=None): """Iterate all ocurances of sub in s Args: s (str): whole string sub (str, list): substring or list of substrings to find Yields: index of start of sub in s """ if not isinstance(substrings, (list, tuple)): substrings = [substrings] while start >= 0: min_found = -1 sublen = 0 for sub in substrings: ind = s.find(sub, start, end) if ind >= 0 and (ind < min_found or min_found == -1): min_found = ind sublen = len(sub) start = min_found if start == -1: return yield start start += sublen # use start += 1 to find overlapping matches
[docs]def rfind_all(s, substrings, start=0, end=None): """Iterate all ocurances of sub in s Args: s (str): whole string sub (str, list): substring or list of substrings to find Yields: index of start of sub in s """ if not isinstance(substrings, (list, tuple)): substrings = [substrings] while start >= 0: max_found = -1 sublen = 0 for sub in substrings: ind = s.rfind(sub, start, end) if ind > max_found: max_found = ind sublen = len(sub) end = max_found if end == -1: return yield end end -= (sublen - 1) # use 0 for overlapping matches
[docs]def loads(text, **kwargs): """Wrap Python's :py:func:`json.loads` Trims out comments and trailing commas first Args: text: json as a string **kwargs: passed to Python's :py:func:`json.loads` """ lines = text.split('\n') last_nonempty = -1 for i in range(len(lines)): # remove comments for loc in find_all(lines[i], ('#', '//')): # if there are an odd number of single or double quotes, then # the comment char is part of a string, and should be kept if (lines[i].count('"', 0, loc) % 2 == 0 and lines[i].count("'", 0, loc) % 2 == 0): # pylint: disable=bad-continuation lines[i] = lines[i][:loc] break lines[i] = lines[i].rstrip() # trailing comma? if lines[i]: remove_at = [] for loc in find_all(lines[i], ('}', ']')): preceeding_txt = lines[i][:loc].rstrip() # if no preceeding_txt on same line, check last_nonwhite if not preceeding_txt: if last_nonempty > -1: if lines[last_nonempty][-1] == ',': lines[last_nonempty] = lines[last_nonempty][:-1] else: if preceeding_txt[-1] == ',': remove_at.append(len(preceeding_txt) - 1) for j, loc in enumerate(remove_at): loc -= j lines[i] = lines[i][:loc] + lines[i][loc + 1:] if lines[i]: last_nonempty = i return _json.loads('\n'.join(lines), **kwargs)
[docs]def load(f, **kwargs): """Read file and pass to :py:func:`loads` Args: f: file-like object with a read method **kwargs: passed to :py:func:`loads` """ try: return loads(f.read(), **kwargs) except Exception: print("JSON Error on file: {0}".format(f.name), file=sys.stderr) raise
[docs]def dumps(obj, **kwargs): """Wrap Python's :py:func:`json.dumps` Args: obj: some object that Python's :py:func:`json.dumps` understands **kwargs: passed to Python's :py:func:`json.dumps` """ return _json.dumps(obj, **kwargs)
[docs]def dump(obj, f, **kwargs): """Wrap Python's :py:func:`json.dump` Args: obj: some object that Python's :py:func:`json.dump` understands f: file-like object with a write method **kwargs: passed to Python's :py:func:`json.dump` """ return _json.dump(obj, f, **kwargs)
## ## EOF ##