[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