Package Products :: Package ZenUtils :: Module jsonutils |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.jsonutils

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 or (at your
      8  # option) any later version as published by the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  import json as _json
     15  import re
     16  from array import array
     17
     18 -def _recursiveCaster(ob):
     19      if isinstance(ob, dict):
     20          result = {}
     21          for k, v in ob.iteritems():
     22              result[str(k)] = _recursiveCaster(v)
     23          return result
     24      elif isinstance(ob, list):
     25          return [_recursiveCaster(x) for x in ob]
     26      elif isinstance(ob, unicode):
     27          return str(ob)
     28      else:
     29          return ob
     30
     31
     32 -class StringifyingDecoder(_json.JSONDecoder):
     33      """
     34      Casts all unicode objects as strings. This is necessary until Zope is less
     35      stupid.
     36      """
     37 -    def decode(self, s):
     38          result = super(StringifyingDecoder, self).decode(s)
     39          return _recursiveCaster(result)
     40
     41 -class JavaScript(object):
     42      """A simple class that represents a JavaScript literal that should not be JSON encoded."""
     43 -    def __init__(self, value):
     44          self.value = value
     45
     46 -    def __str__(self):
     47          return self.value
     48
     49 -class JavaScriptRegex(JavaScript):
     50      """A simple class that represents a JavaScript Regex literal that should not be JSON encoded."""
     51 -    def __str__(self):
     52          return '/' + self.value + '/'
     53
     54 -class ObjectEncoder(_json.JSONEncoder):
     55      _array_converters = { 'c':array.tostring,
     56                            'u':array.tounicode,
     57                          }
     58 -    def default(self, obj):
     59          if hasattr(obj, '__json__') and callable(obj.__json__):
     60              return obj.__json__()
     61          if isinstance(obj, array):
     62              return self._array_converters.get(obj.typecode, array.tolist)(obj)
     63          return super(ObjectEncoder,self).default(obj)
     64
     65 -class JavaScriptEncoder(ObjectEncoder):
     66      """A JavaScript encoder based on JSON. It encodes like normal JSON except it passes JavaScript objects un-encoded."""
     67
     68      _js_start = '__js_start__'
     69      _js_end = '__js_end__'
     70      _js_re = re.compile(r'\["%s", (.*?), "%s"\]' % (_js_start, _js_end))
     71
     72 -    def default(self, obj):
     73          if isinstance(obj, JavaScript):
     74              return [self._js_start, str(obj), self._js_end]
     75
     76          return super(JavaScriptEncoder,self).default(obj)
     77
     78 -    def _js_clean(self, jsonstr):
     79          # This re replace is not ideal but at least the dirtyness of it is encapsulated in these classes
     80          # instead of plain str manipulation being done in the wild.
     81          def fix(matchobj):
     82              return _json.loads(matchobj.group(1))
     83
     84          return self._js_re.sub(fix, jsonstr)
     85
     86 -    def encode(self, obj):
     87          return self._js_clean(super(JavaScriptEncoder,self).encode(obj))
     88
     89 -def _sanitize_value(value, errors='replace'):
     90      """
     91      JSONEncoder doesn't allow overriding the encoding of built-in types
     92      (in particular strings), and allows specifying an encoding but not
     93      a policy for errors when decoding strings to UTF-8. This function
     94      replaces all strings in a nested collection with unicode strings
     95      with 'replace' as the error policy.
     96      """
     97      newvalue = value
     98      if isinstance(value,str):
     99          newvalue = value.decode('utf8', errors)
    100      elif isinstance(value, dict):
    101          newvalue = {}
    102          for k, v in value.iteritems():
    103              if isinstance(v, (str,set,list,dict,tuple)):
    104                  newvalue[k] = _sanitize_value(v)
    105              else:
    106                  newvalue[k] = v
    107      elif isinstance(value,(list,tuple)):
    108          newvalue = []
    109          for v in value:
    110              if isinstance(v, (str,set,list,dict,tuple)):
    111                  newvalue.append(_sanitize_value(v))
    112              else:
    113                  newvalue.append(v)
    114      elif isinstance(value,set):
    115          newvalue = set()
    116          for v in value:
    117              if isinstance(v, (str,set,list,dict,tuple)):
    118                  newvalue.add(_sanitize_value(v))
    119              else:
    120                  newvalue.add(v)
    121
    122      return newvalue
    123
    124 -def json(value, **kw):
    125      """
    126      Serialize C{value} into a JSON string.
    127
    128      If C{value} is callable, a decorated version of C{value} that serializes its
    129      return value will be returned.
    130
    131          >>> value = (dict(a=1L), u"123", 123)
    132          >>> print json(value)
    133          [{"a": 1}, "123", 123]
    134          >>> @json
    135          ... def f():
    136          ...     return value
    137          ...
    138          >>> print f()
    139          [{"a": 1}, "123", 123]
    140          >>> from array import array
    141          >>> a1 = array('i', list(range(10)))
    142          >>> a2 = array('c', 'XYZZY')
    143          >>> a3 = (array('u',[unichr(i) for i in range(250,260)]))
    144          >>> [json(s) for s in (a1, a2, a3)]
    145          ['[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', '"XYZZY"', '"\\\\u00fa\\\\u00fb\\\\u00fc\\\\u00fd\\\\u00fe\\\\u00ff\\\\u0100\\\\u0101\\\\u0102\\\\u0103"']
    146          >>> json([a1, a2, a3])
    147          '[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "XYZZY", "\\\\u00fa\\\\u00fb\\\\u00fc\\\\u00fd\\\\u00fe\\\\u00ff\\\\u0100\\\\u0101\\\\u0102\\\\u0103"]'
    148          >>> json({'properties' : [{ 'key' : 'a1', 'value' : a1 },{ 'key' : 'a2', 'value' : a2 },{ 'key' : 'a3', 'value' : a3 },] })
    149          '{"properties": [{"value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "key": "a1"}, {"value": "XYZZY", "key": "a2"}, {"value": "\\\\u00fa\\\\u00fb\\\\u00fc\\\\u00fd\\\\u00fe\\\\u00ff\\\\u0100\\\\u0101\\\\u0102\\\\u0103", "key": "a3"}]}'
    150
    151      @param value: An object to be serialized
    152      @type value: dict, list, tuple, str, etc. or callable
    153      @return: The JSON representation of C{value} or a decorated function
    154      @rtype: str, func
    155      """
    156      if callable(value):
    157          # Decorate the given callable
    158          def inner(*args, **kwargs):
    159              return json(value(*args, **kwargs))
    160          # Well-behaved decorators look like the decorated function
    161          inner.__name__ = value.__name__
    162          inner.__dict__.update(value.__dict__)
    163          inner.__doc__ = value.__doc__
    164          return inner
    165      else:
    166          # Simply serialize the value
    167          try:
    168              return _json.dumps(value, cls=ObjectEncoder, **kw)
    169          except UnicodeDecodeError:
    170              sanitized = _sanitize_value(value)
    171              return _json.dumps(sanitized, cls=ObjectEncoder, **kw)
    172
    173 -def javascript(value):
    174      """A JavaScript encoder based on JSON. It encodes like normal JSON except it passes JavaScript objects un-encoded."""
    175      if callable(value):
    176          # Decorate the given callable
    177          def inner(*args, **kwargs):
    178              return javascript(value(*args, **kwargs))
    179          # Well-behaved decorators look like the decorated function
    180          inner.__name__ = value.__name__
    181          inner.__dict__.update(value.__dict__)
    182          inner.__doc__ = value.__doc__
    183          return inner
    184      else:
    185          # Simply serialize the value passed
    186          return _json.dumps(value, cls=JavaScriptEncoder)
    187
    188 -def unjson(value, **kw):
    189      """
    190      Create the Python object represented by the JSON string C{value}.
    191
    192          >>> jsonstr = '[{"a": 1}, "123", 123]'
    193          >>> print unjson(jsonstr)
    194          [{'a': 1}, '123', 123]
    195
    196      @param value: A JSON string
    197      @type value: str
    198      @return: The object represented by C{value}
    199      """
    200      if 'cls' not in kw:
    201          kw['cls'] = StringifyingDecoder
    202      return _json.loads(value, **kw)
    203