added the ability to use cjson for json encoding/decoding. gracefully falls back...
[OpenSRF.git] / src / python / osrf / json.py
1 import simplejson, types 
2 from osrf.net_obj import NetworkObject, parse_net_object
3 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
4 import osrf.log
5
6 try:
7     # if available, use the faster cjson module for encoding/decoding JSON
8     import cjson
9     _use_cjson = True
10 except ImportError:
11     _use_cjson = False
12
13 class NetworkEncoder(simplejson.JSONEncoder):
14     ''' Encoder used by simplejson '''
15     def default(self, obj):
16
17         if isinstance(obj, NetworkObject):
18             reg = obj.get_registry()
19             data = obj.get_data()
20
21             # re-encode the object as an array if necessary
22             if reg.protocol == 'array':
23                 objarray = []
24                 for key in reg.keys:
25                     objarray.append(data.get(key)) 
26                 data = objarray
27
28             return { 
29                 OSRF_JSON_CLASS_KEY: reg.hint,
30                 OSRF_JSON_PAYLOAD_KEY: self.default(data)
31             }   
32         return obj
33
34
35 def encode_object(obj):
36     ''' Generic opensrf object encoder, used by cjson '''
37
38     if isinstance(obj, dict):
39         newobj = {}
40         for k,v in obj.iteritems():
41             newobj[k] = encode_object(v)
42         return newobj
43
44     else:
45         if isinstance(obj, list):
46             return [encode_object(v) for v in obj]
47
48         else:
49             if isinstance(obj, NetworkObject):
50                 reg = obj.get_registry()
51                 data = obj.get_data()
52                 if reg.protocol == 'array':
53                     objarray = []
54                     for key in reg.keys:
55                         objarray.append(data.get(key)) 
56                     data = objarray
57
58                 return {
59                     OSRF_JSON_CLASS_KEY: reg.hint,
60                     OSRF_JSON_PAYLOAD_KEY: encode_object(data)
61                 }
62
63     return obj
64         
65
66
67 def to_json(obj):
68     """Turns a python object into a wrapped JSON object"""
69     if _use_cjson:
70         return cjson.encode(encode_object(obj))
71     return simplejson.dumps(obj, cls=NetworkEncoder)
72
73
74 def to_object(json):
75     """Turns a JSON string into python objects"""
76     if _use_cjson:
77         return parse_net_object(cjson.decode(json))
78     return parse_net_object(simplejson.loads(json))
79
80 def parse_json_raw(json):
81     """Parses JSON the old fashioned way."""
82     if _use_cjson:
83         return cjson.decode(json)
84     return simplejson.loads(json)
85
86 def to_json_raw(obj):
87     """Stringifies an object as JSON with no additional logic."""
88     if _use_cjson:
89         return cjson.encode(json)
90     return simplejson.dumps(obj)
91
92 def __tabs(depth):
93     space = ''
94     for i in range(depth):
95         space += '   '
96     return space
97
98 def debug_net_object(obj, depth=1):
99     """Returns a debug string for a given object.
100
101     If it's an NetworkObject and has registered keys, key/value pairs
102     are returned.  Otherwise formatted JSON is returned"""
103
104     debug_str = ''
105     if isinstance(obj, NetworkObject):
106         reg = obj.get_registry()
107         keys = list(reg.keys) # clone it, so sorting won't break the original
108         keys.sort()
109
110         for k in keys:
111
112             key = str(k)
113             while len(key) < 24:
114                 key += '.' # pad the names to make the values line up somewhat
115             val = getattr(obj, k)()
116
117             subobj = val and not (isinstance(val, unicode) or isinstance(val, str) or \
118                 isinstance(val, int) or isinstance(val, float) or isinstance(val, long))
119
120             debug_str += __tabs(depth) + key + ' = '
121
122             if subobj:
123                 debug_str += '\n'
124                 val = debug_net_object(val, depth+1)
125
126             debug_str += str(val)
127
128             if not subobj: debug_str += '\n'
129
130     else:
131         osrf.log.log_internal("Pretty-printing NetworkObject")
132         debug_str = pprint(to_json(obj))
133     return debug_str
134
135 def pprint(json):
136     """JSON pretty-printer"""
137     r = ''
138     t = 0
139     instring = False
140     inescape = False
141     done = False
142     eatws = False
143
144     for c in json:
145
146         if eatws and not _use_cjson: # simpljson adds a pesky space after array and object items
147             if c == ' ': 
148                 continue
149
150         eatws = False
151         done = False
152         if (c == '{' or c == '[') and not instring:
153             t += 1
154             r += c + '\n' + __tabs(t)
155             done = True
156
157         if (c == '}' or c == ']') and not instring:
158             t -= 1
159             r += '\n' + __tabs(t) + c
160             done = True
161
162         if c == ',' and not instring:
163             r += c + '\n' + __tabs(t)
164             done = True
165             eatws = True
166
167         if c == ':' and not instring:
168             eatws = True
169
170         if c == '"' and not inescape:
171             instring = not instring
172
173         if inescape: 
174             inescape = False
175
176         if c == '\\':
177             inescape = True
178
179         if not done:
180             r += c
181
182     return r