Skip to content

Instantly share code, notes, and snippets.

@mtth
Created September 11, 2015 15:09
Show Gist options
  • Save mtth/4f11c25b026e7818588f to your computer and use it in GitHub Desktop.
Save mtth/4f11c25b026e7818588f to your computer and use it in GitHub Desktop.
JSON encoding with variable expansion
#!/usr/bin/env python
# encoding: utf-8
"""JSON serialization with value expansion."""
from json import JSONEncoder
import re
# Try 1.
class ExpandingEncoder(JSONEncoder):
"""JSON encoder which expands `${...}` values.
:param registry: Dictionary of values to use for variable substitution.
Sample usage:
.. code-block:: python
obj = {'one': '${un}', 'un': 1}
encoder = ExpandingEncoder(obj)
encoder.encode(obj) # == {'one': 1, 'un': 1}
Comments:
+ This is nice because expansion doesn't require any pre-processing on the
decoded object. Everything is done at encoding time.
+ This doesn't support other encoder keyword arguments (e.g. `indent`).
"""
pattern = re.compile(r'\${([^}]+)}')
def __init__(self, registry):
super(ExpandingEncoder, self).__init__()
self.registry = registry
def encode(self, obj):
if isinstance(obj, dict):
elems = ('"%s": %s' % (k, self.encode(v)) for k, v in obj.items())
return '{%s}' % (', '.join(elems), )
if isinstance(obj, list):
return '[%s]' % (', '.join(self.encode(e) for e in obj), )
if isinstance(obj, basestring):
match = self.pattern.match(obj)
if match:
return self.encode(self.registry[match.group(1)])
return super(ExpandingEncoder, self).encode(obj)
# Try 2.
class Variable(object):
"""A wrapped `${...}` value.
:param name: The variable's name, used for expansion.
"""
pattern = re.compile(r'\${([^}]+)}')
def __init__(self, name):
self.name = name
@classmethod
def hook(cls, obj):
"""JSON decoder object hook, used to detect and create variables.
:param obj: Decoded object to wrap variables for.
This function should be passed to a JSON decoder as `object_hook` keyword
argument.
"""
for key, value in obj.items():
if isinstance(value, basestring):
match = cls.pattern.match(value)
if match:
obj[key] = cls(match.group(1))
return obj
class VariableEncoder(JSONEncoder):
"""JSON encoder performing variable expansion.
:param registry: Dictionary of values to use for variable substitution.
:param \*\*kwargs: Keyword arguments passed to the base encoder class.
Sample usage:
.. code-block:: python
from json import loads
obj = loads('{"one": "${un}", "un": 1}', object_hook=Variable.hook)
encoder = VariableEncoder(obj)
encoder.encode(obj) # == {'one': 1, 'un': 1}
Comments:
+ The object to encode must have had all its variable wrapped.
+ This supports all the base JSON encoder's keyword arguments.
"""
def __init__(self, registry=None, **kwargs):
super(VariableEncoder, self).__init__(**kwargs)
self.registry = registry or {}
def default(self, obj):
"""Overriden default implementation."""
if isinstance(obj, Variable):
return self.registry[obj.name]
return super(VariableEncoder, self).default(obj)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment