Files
deb-python-json-pointer/jsonpointer.py
2012-11-11 10:43:58 +01:00

249 lines
7.0 KiB
Python

# -*- coding: utf-8 -*-
#
# python-json-pointer - An implementation of the JSON Pointer syntax
# https://github.com/stefankoegl/python-json-pointer
#
# Copyright (c) 2011 Stefan Kögl <stefan@skoegl.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
""" Identify specific nodes in a JSON document (according to draft 05) """
# http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-05
# Will be parsed by setup.py to determine package metadata
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
__version__ = '0.4'
__website__ = 'https://github.com/stefankoegl/python-json-pointer'
__license__ = 'Modified BSD License'
try:
from urllib import unquote
from itertools import izip
except ImportError: # Python 3
from urllib.parse import unquote
izip = zip
from itertools import tee
class JsonPointerException(Exception):
pass
class EndOfList(object):
""" Result of accessing element "-" of a list """
def __init__(self, list_):
self.list_ = list_
def __repr__(self):
return '{cls}({lst})'.format(cls=self.__class__.__name__,
lst=repr(self.list_))
_nothing = object()
def resolve_pointer(doc, pointer, default=_nothing):
"""
Resolves pointer against doc and returns the referenced object
>>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
>>> resolve_pointer(obj, '') == obj
True
>>> resolve_pointer(obj, '/foo') == obj['foo']
True
>>> resolve_pointer(obj, '/foo/another%20prop') == obj['foo']['another prop']
True
>>> resolve_pointer(obj, '/foo/another%20prop/baz') == obj['foo']['another prop']['baz']
True
>>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
True
>>> resolve_pointer(obj, '/some/path', None) == None
True
"""
pointer = JsonPointer(pointer)
return pointer.resolve(doc, default)
def set_pointer(doc, pointer, value):
"""
Set a field to a given value
The field is indicates by a base location that is given in the constructor,
and an optional relative location in the call to set. If the path doesn't
exist, it is created if possible
>>> obj = {"foo": 2}
>>> pointer = JsonPointer('/bar')
>>> pointer.set(obj, 'one', '0')
>>> pointer.set(obj, 'two', '1')
>>> obj == {'foo': 2, 'bar': ['one', 'two']}
True
>>> obj = {"foo": 2, "bar": []}
>>> pointer = JsonPointer('/bar')
>>> pointer.set(obj, 5, '0/x')
>>> obj == {'foo': 2, 'bar': [{'x': 5}]}
True
>>> obj = {'foo': 2, 'bar': [{'x': 5}]}
>>> pointer = JsonPointer('/bar/0')
>>> pointer.set(obj, 10, 'y/0')
>>> obj == {'foo': 2, 'bar': [{'y': [10], 'x': 5}]}
True
"""
pointer = JsonPointer(pointer)
pointer.set(doc, value)
class JsonPointer(object):
""" A JSON Pointer that can reference parts of an JSON document """
def __init__(self, pointer):
parts = pointer.split('/')
if parts.pop(0) != '':
raise JsonPointerException('location must starts with /')
parts = map(unquote, parts)
parts = [part.replace('~1', '/') for part in parts]
parts = [part.replace('~0', '~') for part in parts]
self.parts = parts
def resolve(self, doc, default=_nothing):
"""Resolves the pointer against doc and returns the referenced object"""
for part in self.parts:
try:
doc = self.walk(doc, part)
except JsonPointerException:
if default is _nothing:
raise
else:
return default
return doc
get = resolve
def set(self, doc, value, path=None):
""" Sets a field of doc to value
The location of the field is given by the pointers base location and
the optional path which is relative to the base location """
fullpath = list(self.parts)
if path:
fullpath += path.split('/')
for part, nextpart in pairwise(fullpath):
try:
doc = self.walk(doc, part)
except JsonPointerException:
step_val = [] if nextpart.isdigit() else {}
doc = self._set_value(doc, part, step_val)
self._set_value(doc, fullpath[-1], value)
@staticmethod
def _set_value(doc, part, value):
part = int(part) if part.isdigit() else part
if isinstance(doc, dict):
doc[part] = value
if isinstance(doc, list):
if len(doc) < part:
doc[part] = value
if len(doc) == part:
doc.append(value)
else:
raise IndexError
return doc[part]
def walk(self, doc, part):
""" Walks one step in doc and returns the referenced part """
if isinstance(doc, dict):
try:
return doc[part]
except KeyError:
raise JsonPointerException("member '%s' not found in %s" % (part, doc))
elif isinstance(doc, list):
if part == '-':
return EndOfList(doc)
try:
part = int(part)
except ValueError:
raise JsonPointerException("'%s' is not a valid list index" % (part, ))
try:
return doc[part]
except IndexError:
raise JsonPointerException("index '%s' is out of bounds" % (part, ))
else:
raise JsonPointerException("can not go beyond '%s' (type '%s')" % (part, doc.__class__))
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
for _ in b:
break
return izip(a, b)