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

# Source Code for Module Products.ZenUtils.jsonutils

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