Created
September 11, 2015 15:09
-
-
Save mtth/4f11c25b026e7818588f to your computer and use it in GitHub Desktop.
JSON encoding with variable expansion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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