[Checkins] SVN: zope3org/trunk/src/simplejson/ Updated simplejson
to version 1.3
Uwe Oestermeier
uwe_oestermeier at iwm-kmrc.de
Tue Apr 4 09:30:43 EDT 2006
Log message for revision 66383:
Updated simplejson to version 1.3
Changed:
U zope3org/trunk/src/simplejson/__init__.py
U zope3org/trunk/src/simplejson/decoder.py
U zope3org/trunk/src/simplejson/encoder.py
A zope3org/trunk/src/simplejson/jsonfilter.py
U zope3org/trunk/src/simplejson/scanner.py
-=-
Modified: zope3org/trunk/src/simplejson/__init__.py
===================================================================
--- zope3org/trunk/src/simplejson/__init__.py 2006-04-04 11:26:34 UTC (rev 66382)
+++ zope3org/trunk/src/simplejson/__init__.py 2006-04-04 13:30:42 UTC (rev 66383)
@@ -12,13 +12,15 @@
>>> import simplejson
>>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
- '["foo", {"bar":["baz", null, 1.0, 2]}]'
+ '["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print simplejson.dumps("\"foo\bar")
"\"foo\bar"
>>> print simplejson.dumps(u'\u1234')
"\u1234"
>>> print simplejson.dumps('\\')
"\\"
+ >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+ {"a": 0, "b": 0, "c": 0}
>>> from StringIO import StringIO
>>> io = StringIO()
>>> simplejson.dump(['streaming API'], io)
@@ -37,6 +39,18 @@
>>> simplejson.load(io)
[u'streaming API']
+Specializing JSON object decoding::
+
+ >>> import simplejson
+ >>> def as_complex(dct):
+ ... if '__complex__' in dct:
+ ... return complex(dct['real'], dct['imag'])
+ ... return dct
+ ...
+ >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}',
+ ... object_hook=as_complex)
+ (1+2j)
+
Extending JSONEncoder::
>>> import simplejson
@@ -57,7 +71,7 @@
Note that the JSON produced by this module is a subset of YAML,
so it may be used as a serializer for that as well.
"""
-__version__ = '1.1'
+__version__ = '1.3'
__all__ = [
'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONEncoder',
@@ -136,7 +150,7 @@
return cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, **kw).encode(obj)
-def load(fp, encoding=None, cls=None, **kw):
+def load(fp, encoding=None, cls=None, object_hook=None, **kw):
"""
Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
a JSON document) to a Python object.
@@ -147,15 +161,22 @@
not allowed, and should be wrapped with
``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
object and passed to ``loads()``
+
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg.
"""
if cls is None:
cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
return cls(encoding=encoding, **kw).decode(fp.read())
-def loads(s, encoding=None, cls=None, **kw):
+def loads(s, encoding=None, cls=None, object_hook=None, **kw):
"""
Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
document) to a Python object.
@@ -165,11 +186,18 @@
must be specified. Encodings that are not ASCII based (such as UCS-2)
are not allowed and should be decoded to ``unicode`` first.
+ ``object_hook`` is an optional function that will be called with the
+ result of any object literal decode (a ``dict``). The return value of
+ ``object_hook`` will be used instead of the ``dict``. This feature
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg.
"""
if cls is None:
cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
return cls(encoding=encoding, **kw).decode(s)
def read(s):
Modified: zope3org/trunk/src/simplejson/decoder.py
===================================================================
--- zope3org/trunk/src/simplejson/decoder.py 2006-04-04 11:26:34 UTC (rev 66382)
+++ zope3org/trunk/src/simplejson/decoder.py 2006-04-04 13:30:42 UTC (rev 66383)
@@ -34,30 +34,19 @@
return '%s: line %d column %d - line %d column %d (char %d - %d)' % (
msg, lineno, colno, endlineno, endcolno, pos, end)
-def JSONInfinity(match, context):
- return PosInf, None
-pattern('Infinity')(JSONInfinity)
+_CONSTANTS = {
+ '-Infinity': NegInf,
+ 'Infinity': PosInf,
+ 'NaN': NaN,
+ 'true': True,
+ 'false': False,
+ 'null': None,
+}
-def JSONNegInfinity(match, context):
- return NegInf, None
-pattern('-Infinity')(JSONNegInfinity)
+def JSONConstant(match, context, c=_CONSTANTS):
+ return c[match.group(0)], None
+pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant)
-def JSONNaN(match, context):
- return NaN, None
-pattern('NaN')(JSONNaN)
-
-def JSONTrue(match, context):
- return True, None
-pattern('true')(JSONTrue)
-
-def JSONFalse(match, context):
- return False, None
-pattern('false')(JSONFalse)
-
-def JSONNull(match, context):
- return None, None
-pattern('null')(JSONNull)
-
def JSONNumber(match, context):
match = JSONNumber.regex.match(match.string, *match.span())
integer, frac, exp = match.groups()
@@ -68,8 +57,7 @@
return res, None
pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber)
-STRINGCHUNK = re.compile(r'("|\\|[^"\\]+)', FLAGS)
-STRINGBACKSLASH = re.compile(r'([\\/bfnrt"]|u[A-Fa-f0-9]{4})', FLAGS)
+STRINGCHUNK = re.compile(r'(.*?)(["\\])', FLAGS)
BACKSLASH = {
'"': u'"', '\\': u'\\', '/': u'/',
'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
@@ -77,29 +65,47 @@
DEFAULT_ENCODING = "utf-8"
-def scanstring(s, end, encoding=None):
+def scanstring(s, end, encoding=None, _b=BACKSLASH, _m=STRINGCHUNK.match):
if encoding is None:
encoding = DEFAULT_ENCODING
chunks = []
+ _append = chunks.append
+ begin = end - 1
while 1:
- chunk = STRINGCHUNK.match(s, end)
+ chunk = _m(s, end)
+ if chunk is None:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
end = chunk.end()
- m = chunk.group(1)
- if m == '"':
+ content, terminator = chunk.groups()
+ if content:
+ if not isinstance(content, unicode):
+ content = unicode(content, encoding)
+ _append(content)
+ if terminator == '"':
break
- if m == '\\':
- chunk = STRINGBACKSLASH.match(s, end)
- if chunk is None:
- raise ValueError(errmsg("Invalid \\escape", s, end))
- end = chunk.end()
- esc = chunk.group(1)
+ try:
+ esc = s[end]
+ except IndexError:
+ raise ValueError(
+ errmsg("Unterminated string starting at", s, begin))
+ if esc != 'u':
try:
- m = BACKSLASH[esc]
+ m = _b[esc]
except KeyError:
- m = unichr(int(esc[1:], 16))
- if not isinstance(m, unicode):
- m = unicode(m, encoding)
- chunks.append(m)
+ raise ValueError(
+ errmsg("Invalid \\escape: %r" % (esc,), s, end))
+ end += 1
+ else:
+ esc = s[end + 1:end + 5]
+ try:
+ m = unichr(int(esc, 16))
+ if len(esc) != 4 or not esc.isalnum():
+ raise ValueError
+ except ValueError:
+ raise ValueError(errmsg("Invalid \\uXXXX escape", s, end))
+ end += 5
+ _append(m)
return u''.join(chunks), end
def JSONString(match, context):
@@ -107,18 +113,12 @@
return scanstring(match.string, match.end(), encoding)
pattern(r'"')(JSONString)
-WHITESPACE = re.compile(r'\s+', FLAGS)
+WHITESPACE = re.compile(r'\s*', FLAGS)
-def skipwhitespace(s, end):
- m = WHITESPACE.match(s, end)
- if m is not None:
- return m.end()
- return end
-
-def JSONObject(match, context):
+def JSONObject(match, context, _w=WHITESPACE.match):
pairs = {}
s = match.string
- end = skipwhitespace(s, match.end())
+ end = _w(s, match.end()).end()
nextchar = s[end:end + 1]
# trivial empty object
if nextchar == '}':
@@ -129,34 +129,37 @@
encoding = getattr(context, 'encoding', None)
while True:
key, end = scanstring(s, end, encoding)
- end = skipwhitespace(s, end)
+ end = _w(s, end).end()
if s[end:end + 1] != ':':
raise ValueError(errmsg("Expecting : delimiter", s, end))
- end = skipwhitespace(s, end + 1)
+ end = _w(s, end + 1).end()
try:
value, end = JSONScanner.iterscan(s, idx=end).next()
except StopIteration:
raise ValueError(errmsg("Expecting object", s, end))
pairs[key] = value
- end = skipwhitespace(s, end)
+ end = _w(s, end).end()
nextchar = s[end:end + 1]
end += 1
if nextchar == '}':
break
if nextchar != ',':
raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
- end = skipwhitespace(s, end)
+ end = _w(s, end).end()
nextchar = s[end:end + 1]
end += 1
if nextchar != '"':
raise ValueError(errmsg("Expecting property name", s, end - 1))
+ object_hook = getattr(context, 'object_hook', None)
+ if object_hook is not None:
+ pairs = object_hook(pairs)
return pairs, end
pattern(r'{')(JSONObject)
-def JSONArray(match, context):
+def JSONArray(match, context, _w=WHITESPACE.match):
values = []
s = match.string
- end = skipwhitespace(s, match.end())
+ end = _w(s, match.end()).end()
# look-ahead for trivial empty array
nextchar = s[end:end + 1]
if nextchar == ']':
@@ -167,28 +170,23 @@
except StopIteration:
raise ValueError(errmsg("Expecting object", s, end))
values.append(value)
- end = skipwhitespace(s, end)
+ end = _w(s, end).end()
nextchar = s[end:end + 1]
end += 1
if nextchar == ']':
break
if nextchar != ',':
raise ValueError(errmsg("Expecting , delimiter", s, end))
- end = skipwhitespace(s, end)
+ end = _w(s, end).end()
return values, end
pattern(r'\[')(JSONArray)
ANYTHING = [
- JSONTrue,
- JSONFalse,
- JSONNull,
- JSONNaN,
- JSONInfinity,
- JSONNegInfinity,
+ JSONObject,
+ JSONArray,
+ JSONString,
+ JSONConstant,
JSONNumber,
- JSONString,
- JSONArray,
- JSONObject,
]
JSONScanner = Scanner(ANYTHING)
@@ -226,7 +224,7 @@
_scanner = Scanner(ANYTHING)
__all__ = ['__init__', 'decode', 'raw_decode']
- def __init__(self, encoding=None):
+ def __init__(self, encoding=None, object_hook=None):
"""
``encoding`` determines the encoding used to interpret any ``str``
objects decoded by this instance (utf-8 by default). It has no
@@ -234,16 +232,22 @@
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as ``unicode``.
+
+ ``object_hook``, if specified, will be called with the result
+ of every JSON object decoded and its return value will be used in
+ place of the given ``dict``. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
"""
self.encoding = encoding
+ self.object_hook = object_hook
- def decode(self, s):
+ def decode(self, s, _w=WHITESPACE.match):
"""
Return the Python representation of ``s`` (a ``str`` or ``unicode``
instance containing a JSON document)
"""
- obj, end = self.raw_decode(s, idx=skipwhitespace(s, 0))
- end = skipwhitespace(s, end)
+ obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+ end = _w(s, end).end()
if end != len(s):
raise ValueError(errmsg("Extra data", s, end, len(s)))
return obj
Modified: zope3org/trunk/src/simplejson/encoder.py
===================================================================
--- zope3org/trunk/src/simplejson/encoder.py 2006-04-04 11:26:34 UTC (rev 66382)
+++ zope3org/trunk/src/simplejson/encoder.py 2006-04-04 13:30:42 UTC (rev 66383)
@@ -90,18 +90,18 @@
implementation (to raise ``TypeError``).
"""
__all__ = ['__init__', 'default', 'encode', 'iterencode']
- def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True):
+ def __init__(self, skipkeys=False, ensure_ascii=True,
+ check_circular=True, allow_nan=True, sort_keys=False):
"""
Constructor for JSONEncoder, with sensible defaults.
-
+
If skipkeys is False, then it is a TypeError to attempt
encoding of keys that are not str, int, long, float or None. If
skipkeys is True, such items are simply skipped.
If ensure_ascii is True, the output is guaranteed to be str
- objects with all incoming unicode characters escaped. If ensure_ascii
- is false, the output will be unicode object.
+ objects with all incoming unicode characters escaped. If
+ ensure_ascii is false, the output will be unicode object.
If check_circular is True, then lists, dicts, and custom encoded
objects will be checked for circular references during encoding to
@@ -112,12 +112,17 @@
encoded as such. This behavior is not JSON specification compliant,
but is consistent with most JavaScript based encoders and decoders.
Otherwise, it will be a ValueError to encode such floats.
+
+ If sort_keys is True, then the output of dictionaries will be
+ sorted by key; this is useful for regression tests to ensure
+ that JSON serializations can be compared on a day-to-day basis.
"""
-
+
self.skipkeys = skipkeys
self.ensure_ascii = ensure_ascii
self.check_circular = check_circular
self.allow_nan = allow_nan
+ self.sort_keys = sort_keys
def _iterencode_list(self, lst, markers=None):
if not lst:
@@ -157,7 +162,13 @@
else:
encoder = encode_basestring
allow_nan = self.allow_nan
- for key, value in dct.iteritems():
+ if self.sort_keys:
+ keys = dct.keys()
+ keys.sort()
+ items = [(k,dct[k]) for k in keys]
+ else:
+ items = dct.iteritems()
+ for key, value in items:
if isinstance(key, basestring):
pass
# JavaScript is weakly typed for these, so it makes sense to
@@ -181,7 +192,7 @@
else:
yield ', '
yield encoder(key)
- yield ':'
+ yield ': '
for chunk in self._iterencode(value, markers):
yield chunk
yield '}'
Added: zope3org/trunk/src/simplejson/jsonfilter.py
===================================================================
--- zope3org/trunk/src/simplejson/jsonfilter.py 2006-04-04 11:26:34 UTC (rev 66382)
+++ zope3org/trunk/src/simplejson/jsonfilter.py 2006-04-04 13:30:42 UTC (rev 66383)
@@ -0,0 +1,40 @@
+import simplejson
+import cgi
+
+class JSONFilter(object):
+ def __init__(self, app, mime_type='text/x-json'):
+ self.app = app
+ self.mime_type = mime_type
+
+ def __call__(self, environ, start_response):
+ # Read JSON POST input to jsonfilter.json if matching mime type
+ response = {'status': '200 OK', 'headers': []}
+ def json_start_response(status, headers):
+ response['status'] = status
+ response['headers'].extend(headers)
+ environ['jsonfilter.mime_type'] = self.mime_type
+ if environ.get('REQUEST_METHOD', '') == 'POST':
+ if environ.get('CONTENT_TYPE', '') == self.mime_type:
+ args = [_ for _ in [environ.get('CONTENT_LENGTH')] if _]
+ data = environ['wsgi.input'].read(*map(int, args))
+ environ['jsonfilter.json'] = simplejson.loads(data)
+ res = simplejson.dumps(self.app(environ, json_start_response))
+ jsonp = cgi.parse_qs(environ.get('QUERY_STRING', '')).get('jsonp')
+ if jsonp:
+ content_type = 'text/javascript'
+ res = ''.join(jsonp + ['(', res, ')'])
+ elif 'Opera' in environ.get('HTTP_USER_AGENT', ''):
+ # Opera has bunk XMLHttpRequest support for most mime types
+ content_type = 'text/plain'
+ else:
+ content_type = self.mime_type
+ headers = [
+ ('Content-type', content_type),
+ ('Content-length', len(res)),
+ ]
+ headers.extend(response['headers'])
+ start_response(response['status'], headers)
+ return [res]
+
+def factory(app, global_conf, **kw):
+ return JSONFilter(app, **kw)
Modified: zope3org/trunk/src/simplejson/scanner.py
===================================================================
--- zope3org/trunk/src/simplejson/scanner.py 2006-04-04 11:26:34 UTC (rev 66382)
+++ zope3org/trunk/src/simplejson/scanner.py 2006-04-04 13:30:42 UTC (rev 66383)
@@ -61,7 +61,3 @@
fn.regex = re.compile(pattern, flags)
return fn
return decorator
-
-def InsignificantWhitespace(match, context):
- return None, None
-pattern(r'\s+')(InsignificantWhitespace)
More information about the Checkins
mailing list