Skip to content

Handy python classes for manipulating json data, providing syntactic sugar for less verbose, easier to write code.

License

Notifications You must be signed in to change notification settings

aelkner/traversify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

traversify

Handy 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.

Traverser

Pass 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:

>>> from traversify import Traverser
>>> obj = Traverser({'id': 1, 'username': 'jdoe'})
>>> obj.id
1
>>> obj.username
'jdoe'
>>> obj.bad_key is None
True
>>> [node.id for node in obj]
[1]
>>> obj[0].id
1
>>> {'id': 1, 'username': 'jdoe'} in obj
True

Not 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.id for node in obj]
[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
>>> del obj.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}'

Filter

Often 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:

>>> from traversify import Traverser, Filter
>>> id_exclude_filter = Filter(blacklist='id')
>>> record = Traverser({'id': 1, 'username': 'jdoe'})
>>> id_exclude_filter.are_equal(record, {'username': 'jdoe'})
True

The 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 ==, in and the prune() 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'} in record
True
>>> 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'}

About

Handy python classes for manipulating json data, providing syntactic sugar for less verbose, easier to write code.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages