Package: traversify
traversifyHandy python classes for manipulating json data, providing syntactic sugar for less verbose, easier to write code.Traverser class allows one to:traverse complex trees of data with dotted syntax rather than the verbose dictionary dereferencing.treat nodes on the tree as lists even if they are singleton dictionaries, eliminating a lot of type-checking code.add or delete branches of the tree with simple dotted syntax.treat missing keys on the tree as None rather than throwing a key exception, much as JavaScript returns undefined.linkage to Filter class (defined next) for powerful tree comparisons or tree pruning.Filter class allows one to:define a set of criteria for comparing two partially incongruous trees by limiting the sets of fields compared.apply said criteria to prune a tree of any unwanted fields.TraverserPass tree data to Traverser, either as a list, dictionary, json string or any class offering a json method, and the resultant object will provide the syntactic sugar for traversing with dotted syntax, treating singleston nodes as lists:>>>fromtraversifyimportTraverser>>>obj=Traverser({'id':1,'username':'jdoe'})>>>obj.id1>>>obj.username'jdoe'>>>obj.bad_keyisNoneTrue>>>[node.idfornodeinobj][1]>>>obj[0].id1>>>{'id':1,'username':'jdoe'}inobjTrueNot only  can singletons be addressed as lists, but append and extend methods are available to turn singletons into lists on the fly:>>>obj=Traverser({'id':1})>>>obj.append({'id':2})>>>obj.extend([{'id':3,'id':4}])>>>[node.idfornodeinobj][1, 2, 3, 4]At any time, a Traverser instance will return the underlying value when called:>>>obj=Traverser({'id':1})>>>obj(){'id': 1}The tree can be updated using dotted syntax.  Note that by default, a Traverser instance makes a deepcopy of the json data so that there are no unintended side effects:>>>data={'id':1,'username':'jdoe'}>>>obj=Traverser(data)>>>obj.id=2>>>delobj.username>>>obj(){'id': 2}>>>data{'id': 1, 'username': 'jdoe'}However, if the side-effect of updating the data passed is desired (perhaps due to memory constaints), then pass deepcopy=False:>>>data={'id':1}>>>obj=Traverser(data,deepcopy=False)>>>obj.id=2>>>obj(){'id': 2}>>>data{'id': 2}In case there are keys that are not identifiers, then dictionary dereferencing can still be used:>>>obj=Traverser({'@xsi.type':'textarea'})>>>obj['@xsi.type']'textarea'The get method allows traversing multiple levels in one call, using dots to set off the levels:>>>obj=Traverser({'root':{'username':'any'}})>>>obj.get('root.username')'any'Also, the get method supports dot-escaping so that keys containing dots can still be traversed:>>>obj=Traverser({'@xsi.type':'textarea'})>>>obj.get('@xsi..type')'textarea'There's a set method that will update a node multiple levels down and even build out branches that aren't already there:>>>obj=Traverser({'stats':{'id':1}})>>>obj.set('stats.id',2)>>>obj(){'stats': {'id': 2}}>>>obj.set('users.0.username','any')>>>obj(){'stats': {'id': 2}, 'users': [{'username': 'any'}]}To save the trouble of importing json and using dumps, there's a handy to_json method:>>>obj=Traverser({'id':1})>>>obj.to_json()'{"id": 1}'FilterOften one needs to compare two trees without taking into account irrelavant fields, like when records in the tree have ids, but a new record doesn't have it yet.  Filter provides a way to make this less verbose by providing blacklist and whitelist attributes for controlled comparison:>>>fromtraversifyimportTraverser,Filter>>>id_exclude_filter=Filter(blacklist='id')>>>record=Traverser({'id':1,'username':'jdoe'})>>>id_exclude_filter.are_equal(record,{'username':'jdoe'})TrueThe same filter can be used to prune a tree of its unwanted fields:>>>id_exclude_filter.prune(record)>>>record(){'username': 'jdoe'}If a filter is passed while creating a Traverser instance, then==,inand theprune()method will use it to do the comparison or pruning:>>>record=Traverser({'id':1,'username':'jdoe'},filter=Filter(blacklist='id'))>>>record=={'username':'jdoe'}True>>>{'username':'jdoe'}inrecordTrue>>>record.prune()>>>record(){'username': 'jdoe'}Traverser's prune method will accept a filter to override the default (or supply one not already supplied):>>>record=Traverser({'id':1,'username':'jdoe'})>>>record.prune(filter=Filter(blacklist='id'))>>>record(){'username': 'jdoe'}