doc: Add git-cola-1.4.0.4 release notes
[git-cola.git] / jsonpickle / pickler.py
blob07706e39c8a09048e0dcf108fa8b257ff9f97d6a
1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008 John Paulett (john -at- 7oars.com)
4 # All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution.
8 import types
9 import jsonpickle.util as util
10 import jsonpickle.tags as tags
13 class Pickler(object):
14 """Converts a Python object to a JSON representation.
16 Setting unpicklable to False removes the ability to regenerate
17 the objects into object types beyond what the standard
18 simplejson library supports.
20 Setting max_depth to a negative number means there is no
21 limit to the depth jsonpickle should recurse into an
22 object. Setting it to zero or higher places a hard limit
23 on how deep jsonpickle recurses into objects, dictionaries, etc.
25 >>> p = Pickler()
26 >>> p.flatten('hello world')
27 'hello world'
28 """
30 def __init__(self, unpicklable=True, max_depth=None):
31 self.unpicklable = unpicklable
32 ## The current recursion depth
33 self._depth = -1
34 ## The maximal recursion depth
35 self._max_depth = max_depth
36 ## Maps id(obj) to reference names
37 self._objs = {}
38 ## The namestack grows whenever we recurse into a child object
39 self._namestack = []
41 def _reset(self):
42 self._objs = {}
43 self._namestack = []
45 def _push(self):
46 """Steps down one level in the namespace.
47 """
48 self._depth += 1
50 def _pop(self, value):
51 """Step up one level in the namespace and return the value.
52 If we're at the root, reset the pickler's state.
53 """
54 self._depth -= 1
55 if self._depth == -1:
56 self._reset()
57 return value
59 def _mkref(self, obj):
60 objid = id(obj)
61 if objid not in self._objs:
62 self._objs[objid] = '/' + '/'.join(self._namestack)
63 return True
64 return False
66 def _getref(self, obj):
67 return {tags.REF: self._objs.get(id(obj))}
69 def flatten(self, obj):
70 """Takes an object and returns a JSON-safe representation of it.
72 Simply returns any of the basic builtin datatypes
74 >>> p = Pickler()
75 >>> p.flatten('hello world')
76 'hello world'
77 >>> p.flatten(u'hello world')
78 u'hello world'
79 >>> p.flatten(49)
81 >>> p.flatten(350.0)
82 350.0
83 >>> p.flatten(True)
84 True
85 >>> p.flatten(False)
86 False
87 >>> r = p.flatten(None)
88 >>> r is None
89 True
90 >>> p.flatten(False)
91 False
92 >>> p.flatten([1, 2, 3, 4])
93 [1, 2, 3, 4]
94 >>> p.flatten((1,2,))[tags.TUPLE]
95 [1, 2]
96 >>> p.flatten({'key': 'value'})
97 {'key': 'value'}
98 """
100 self._push()
102 if self._depth == self._max_depth:
103 return self._pop(repr(obj))
105 if util.is_primitive(obj):
106 return self._pop(obj)
108 if util.is_list(obj):
109 return self._pop([ self.flatten(v) for v in obj ])
111 # We handle tuples and sets by encoding them in a "(tuple|set)dict"
112 if util.is_tuple(obj):
113 return self._pop({tags.TUPLE: [ self.flatten(v) for v in obj ]})
115 if util.is_set(obj):
116 return self._pop({tags.SET: [ self.flatten(v) for v in obj ]})
118 if util.is_dictionary(obj):
119 return self._pop(self._flatten_dict_obj(obj, obj.__class__()))
121 if util.is_type(obj):
122 return self._pop(_mktyperef(obj))
124 if util.is_object(obj):
125 data = {}
126 has_class = hasattr(obj, '__class__')
127 has_dict = hasattr(obj, '__dict__')
128 if self._mkref(obj):
129 if has_class and not util.is_repr(obj):
130 module, name = _getclassdetail(obj)
131 if self.unpicklable is True:
132 data[tags.OBJECT] = '%s.%s' % (module, name)
134 if util.is_repr(obj):
135 if self.unpicklable is True:
136 data[tags.REPR] = '%s/%s' % (obj.__class__.__module__,
137 repr(obj))
138 else:
139 data = unicode(obj)
140 return self._pop(data)
142 if util.is_dictionary_subclass(obj):
143 return self._pop(self._flatten_dict_obj(obj, data))
145 if util.is_noncomplex(obj):
146 return self._pop([self.flatten(v) for v in obj])
148 if has_dict:
149 return self._pop(self._flatten_dict_obj(obj.__dict__, data))
150 else:
151 # We've seen this object before so place an object
152 # reference tag in the data. This avoids infinite recursion
153 # when processing cyclical objects.
154 return self._pop(self._getref(obj))
156 return self._pop(data)
157 # else, what else? (methods, functions, old style classes...)
159 def _flatten_dict_obj(self, obj, data):
160 """Recursively call flatten() and return json-friendly dict
162 for k, v in obj.iteritems():
163 if util.is_function(v):
164 continue
165 if type(k) not in types.StringTypes:
166 try:
167 k = repr(k)
168 except:
169 k = unicode(k)
170 self._namestack.append(k)
171 data[k] = self.flatten(v)
172 self._namestack.pop()
173 return data
175 def _mktyperef(obj):
176 """Return a typeref dictionary. Used for references.
178 >>> from jsonpickle import tags
179 >>> _mktyperef(AssertionError)[tags.TYPE].rsplit('.', 1)[0]
180 'exceptions'
182 >>> _mktyperef(AssertionError)[tags.TYPE].rsplit('.', 1)[-1]
183 'AssertionError'
185 return {tags.TYPE: '%s.%s' % (obj.__module__, obj.__name__)}
187 def _getclassdetail(obj):
188 """Helper class to return the class of an object.
190 >>> class Example(object): pass
191 >>> _getclassdetail(Example())
192 ('jsonpickle.pickler', 'Example')
193 >>> _getclassdetail(25)
194 ('__builtin__', 'int')
195 >>> _getclassdetail(None)
196 ('__builtin__', 'NoneType')
197 >>> _getclassdetail(False)
198 ('__builtin__', 'bool')
200 cls = obj.__class__
201 module = getattr(cls, '__module__')
202 name = getattr(cls, '__name__')
203 return module, name