Codebase list python-json-pointer / 19f9f21
Support for set_pointer and indexing arbitrary objects via __getitem__/__setitem__ Christopher J. White authored 10 years ago Stefan Kögl committed 10 years ago
3 changed file(s) with 185 addition(s) and 7 deletion(s). Raw diff Collapse all Expand all
66
77 .. code-block:: python
88
9 >>> import jsonpointer
9 >>> from jsonpointer import resolve_pointer
1010 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
1111
1212 >>> resolve_pointer(obj, '') == obj
2828 True
2929
3030
31 The ``set_pointer`` method allows modifying a portion of an object using
32 JSON pointer notation:
33
34 .. code-block:: python
35
36 >>> from jsonpointer import set_pointer
37 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
38
39 >>> set_pointer(obj, '/foo/anArray/0/prop', 55)
40 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
41
42 >>> obj
43 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
44
45 By default ``set_pointer`` modifies the original object. Pass ``inplace=False``
46 to create a copy and modify the copy instead:
47
48 >>> from jsonpointer import set_pointer
49 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
50
51 >>> set_pointer(obj, '/foo/anArray/0/prop', 55, inplace=False)
52 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
53
54 >>> obj
55 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 44}]}}
56
3157 The ``JsonPointer`` class wraps a (string) path and can be used to access the
3258 same path on several objects.
3359
4949
5050 from itertools import tee
5151 import re
52 import copy
5253
5354
5455 # array indices must not contain leading zeros, signs, spaces, decimals, etc
102103
103104 pointer = JsonPointer(pointer)
104105 return pointer.resolve(doc, default)
106
107 def set_pointer(doc, pointer, value, inplace=True):
108 """
109 Resolves pointer against doc and sets the value of the target within doc.
110
111 With inplace set to true, doc is modified as long as pointer is not the
112 root.
113
114 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
115
116 >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
117 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
118 True
119
120 >>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \
121 {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
122 True
123
124 """
125
126 pointer = JsonPointer(pointer)
127 return pointer.set(doc, value, inplace)
105128
106129
107130 class JsonPointer(object):
148171
149172 get = resolve
150173
174 def set(self, doc, value, inplace=True):
175 """ Resolve the pointer against the doc and replace the target with value. """
176
177 if len(self.parts) == 0:
178 if inplace:
179 raise JsonPointerException('cannot set root in place')
180 return value
181
182 if not inplace:
183 doc = copy.deepcopy(doc)
184
185 (parent, part) = self.to_last(doc)
186
187 parent[part] = value
188 return doc
151189
152190 def get_part(self, doc, part):
153191 """ Returns the next step in the correct type """
165203
166204 return int(part)
167205
206 elif hasattr(doc, '__getitem__'):
207 # Allow indexing via ducktyping if the target has defined __getitem__
208 return part
209
168210 else:
169 raise JsonPointerException("Unknown document type '%s'" % (doc.__class__,))
211 raise JsonPointerException("Document '%s' does not support indexing, "
212 "must be dict/list or support __getitem__" % type(doc))
170213
171214
172215 def walk(self, doc, part):
174217
175218 part = self.get_part(doc, part)
176219
177 # type is already checked in get_part, so we assert here
178 # for consistency
179 assert type(doc) in (dict, list), "invalid document type %s" (type(doc),)
220 assert (type(doc) in (dict, list) or hasattr(doc, '__getitem__')), "invalid document type %s" (type(doc))
180221
181222 if isinstance(doc, dict):
182223 try:
196237 except IndexError:
197238 raise JsonPointerException("index '%s' is out of bounds" % (part, ))
198239
240 else:
241 # Object supports __getitem__, assume custom indexing
242 return doc[part]
199243
200244 def contains(self, ptr):
201 """" Returns True if self contains the given ptr """
245 """ Returns True if self contains the given ptr """
202246 return len(self.parts) > len(ptr.parts) and \
203247 self.parts[:len(ptr.parts)] == ptr.parts
204248
33 import doctest
44 import unittest
55 import sys
6 import copy
67 from jsonpointer import resolve_pointer, EndOfList, JsonPointerException, \
7 JsonPointer
8 JsonPointer, set_pointer
89
910 class SpecificationTests(unittest.TestCase):
1011 """ Tests all examples from the JSON Pointer specification """
109110 self.assertEqual(nxt, 'b')
110111
111112
113 class SetTests(unittest.TestCase):
114
115 def test_set(self):
116 doc = {
117 "foo": ["bar", "baz"],
118 "": 0,
119 "a/b": 1,
120 "c%d": 2,
121 "e^f": 3,
122 "g|h": 4,
123 "i\\j": 5,
124 "k\"l": 6,
125 " ": 7,
126 "m~n": 8
127 }
128 origdoc = copy.deepcopy(doc)
129
130 # inplace=False
131 newdoc = set_pointer(doc, "/foo/1", "cod", inplace=False)
132 self.assertEqual(resolve_pointer(newdoc, "/foo/1"), "cod")
133
134 newdoc = set_pointer(doc, "/", 9, inplace=False)
135 self.assertEqual(resolve_pointer(newdoc, "/"), 9)
136
137 newdoc = set_pointer(doc, "/fud", {}, inplace=False)
138 newdoc = set_pointer(newdoc, "/fud/gaw", [1, 2, 3], inplace=False)
139 self.assertEqual(resolve_pointer(newdoc, "/fud"), {'gaw' : [1, 2, 3]})
140
141 newdoc = set_pointer(doc, "", 9, inplace=False)
142 self.assertEqual(newdoc, 9)
143
144 self.assertEqual(doc, origdoc)
145
146 # inplace=True
147 set_pointer(doc, "/foo/1", "cod")
148 self.assertEqual(resolve_pointer(doc, "/foo/1"), "cod")
149
150 set_pointer(doc, "/", 9)
151 self.assertEqual(resolve_pointer(doc, "/"), 9)
152
153 self.assertRaises(JsonPointerException, set_pointer, doc, "/fud/gaw", 9)
154
155 set_pointer(doc, "/fud", {})
156 set_pointer(doc, "/fud/gaw", [1, 2, 3] )
157 self.assertEqual(resolve_pointer(doc, "/fud"), {'gaw' : [1, 2, 3]})
158
159 self.assertRaises(JsonPointerException, set_pointer, doc, "", 9)
160
161 class AltTypesTests(unittest.TestCase):
162
163 def test_alttypes(self):
164 JsonPointer.alttypes = True
165
166 class Node(object):
167 def __init__(self, name, parent=None):
168 self.name = name
169 self.parent = parent
170 self.left = None
171 self.right = None
172
173 def set_left(self, node):
174 node.parent = self
175 self.left = node
176
177 def set_right(self, node):
178 node.parent = self
179 self.right = node
180
181 def __getitem__(self, key):
182 if key == 'left':
183 return self.left
184 if key == 'right':
185 return self.right
186
187 raise KeyError("Only left and right supported")
188
189 def __setitem__(self, key, val):
190 if key == 'left':
191 return self.set_left(val)
192 if key == 'right':
193 return self.set_right(val)
194
195 raise KeyError("Only left and right supported: %s" % key)
196
197
198 root = Node('root')
199 root.set_left(Node('a'))
200 root.left.set_left(Node('aa'))
201 root.left.set_right(Node('ab'))
202 root.set_right(Node('b'))
203 root.right.set_left(Node('ba'))
204 root.right.set_right(Node('bb'))
205
206 self.assertEqual(resolve_pointer(root, '/left').name, 'a')
207 self.assertEqual(resolve_pointer(root, '/left/right').name, 'ab')
208 self.assertEqual(resolve_pointer(root, '/right').name, 'b')
209 self.assertEqual(resolve_pointer(root, '/right/left').name, 'ba')
210
211 newroot = set_pointer(root, '/left/right', Node('AB'), inplace=False)
212 self.assertEqual(resolve_pointer(root, '/left/right').name, 'ab')
213 self.assertEqual(resolve_pointer(newroot, '/left/right').name, 'AB')
214
215 set_pointer(root, '/left/right', Node('AB'))
216 self.assertEqual(resolve_pointer(root, '/left/right').name, 'AB')
217
112218 suite = unittest.TestSuite()
113219 suite.addTest(unittest.makeSuite(SpecificationTests))
114220 suite.addTest(unittest.makeSuite(ComparisonTests))
115221 suite.addTest(unittest.makeSuite(WrongInputTests))
116222 suite.addTest(unittest.makeSuite(ToLastTests))
223 suite.addTest(unittest.makeSuite(SetTests))
224 suite.addTest(unittest.makeSuite(AltTypesTests))
117225
118226 modules = ['jsonpointer']
119227