blob: 4252dc6a84c9bd1ed29afe5f717bd4937567db4c [file] [log] [blame]
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import json
import sys
from .parser import Parser
if sys.version_info[0] < 3:
# pylint: disable=redefined-builtin
str = unicode
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None):
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object
containing a JSON document) to a Python object."""
s = fp.read()
return loads(s, encoding=encoding, cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant,
object_pairs_hook=object_pairs_hook)
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None):
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a
JSON5 document) to a Python object."""
assert cls is None, 'Custom decoders are not supported'
if sys.version_info[0] < 3:
decodable_type = type('')
else:
decodable_type = type(b'')
if isinstance(s, decodable_type):
encoding = encoding or 'utf-8'
s = s.decode(encoding)
if not s:
raise ValueError('Empty strings are not legal JSON5')
parser = Parser(s, '<string>')
ast, err, newpos = parser.parse()
if err:
raise ValueError(err)
def _fp_constant_parser(s):
return float(s.replace('Infinity', 'inf').replace('NaN', 'nan'))
if object_pairs_hook:
dictify = object_pairs_hook
elif object_hook:
dictify = lambda pairs: object_hook(dict(pairs))
else:
dictify = dict
parse_float = parse_float or float
parse_int = parse_int or int
parse_constant = parse_constant or _fp_constant_parser
return _walk_ast(ast, dictify, parse_float, parse_int, parse_constant)
def _walk_ast(el, dictify, parse_float, parse_int, parse_constant):
if el == 'None':
return None
if el == 'True':
return True
if el == 'False':
return False
ty, v = el
if ty == 'number':
if v.startswith('0x') or v.startswith('0X'):
return parse_int(v, base=16)
elif '.' in v or 'e' in v or 'E' in v:
return parse_float(v)
elif 'Infinity' in v or 'NaN' in v:
return parse_constant(v)
else:
return parse_int(v)
if ty == 'string':
return v
if ty == 'object':
pairs = []
for key, val_expr in v:
val = _walk_ast(val_expr, dictify, parse_float, parse_int,
parse_constant)
pairs.append((key, val))
return dictify(pairs)
if ty == 'array':
return [_walk_ast(el, dictify, parse_float, parse_int, parse_constant)
for el in v]
raise Exception('unknown el: ' + el) # pragma: no cover
_notletter = re.compile('\W')
def _dumpkey(k):
if _notletter.search(k):
return json.dumps(k)
else:
return str(k)
def dumps(obj, compact=False, as_json=False, **kwargs):
"""Serialize ``obj`` to a JSON5-formatted ``str``."""
if as_json or not compact:
return json.dumps(obj, **kwargs)
t = type(obj)
if obj == True:
return u'true'
elif obj == False:
return u'false'
elif obj == None:
return u'null'
elif t == type('') or t == type(u''):
single = "'" in obj
double = '"' in obj
if single and double:
return json.dumps(obj)
elif single:
return '"' + obj + '"'
else:
return "'" + obj + "'"
elif t is float or t is int:
return str(obj)
elif t is dict:
return u'{' + u','.join([
_dumpkey(k) + u':' + dumps(v) for k, v in obj.items()
]) + '}'
elif t is list:
return u'[' + ','.join([dumps(el) for el in obj]) + u']'
else: # pragma: no cover
return u''
def dump(obj, fp, **kwargs):
"""Serialize ``obj`` to a JSON5-formatted stream to ``fp`` (a ``.write()``-
supporting file-like object)."""
s = dumps(obj, **kwargs)
fp.write(str(s))