Skip to content

Instantly share code, notes, and snippets.

@mariocesar
Last active August 12, 2024 18:17
Show Gist options
  • Save mariocesar/b36e89c52eb12bc18f3158aae15887bd to your computer and use it in GitHub Desktop.
Save mariocesar/b36e89c52eb12bc18f3158aae15887bd to your computer and use it in GitHub Desktop.
A JSON Decoder that will evaluate strings as jinja2 expressions
"""
TODO: Use an strict environment like SandboxedEnvironment
TODO: Create an "allowed list" of filters and functions to use in the expression
TODO: Make or check the context object is inmutable (Prevent thread-safe situations)
"""
import json
from datetime import datetime
from jinja2 import Environment, meta, sandbox
def now(format="%Y-%m-%d %H:%M:%S"):
return datetime.now().strftime(format)
jinja_env = sandbox.SandboxedEnvironment()
jinja_env.globals['now'] = now
def evaluate_jinja_expression(value, context=None):
if context is None:
context = {}
try:
parsed_content = jinja_env.parse(value)
# Check if it contains any variables or blocks
if meta.find_undeclared_variables(parsed_content):
# Render the template with the provided context
template = jinja_env.from_string(value)
return template.render(context)
else:
# DO nothing if no expression is found
return value
except Exception as e:
print(f"Failed to evaluate expression: {e}")
return value
class JinjaJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
self.context = kwargs.pop("context", {})
super().__init__(object_hook=self._deserialize, *args, **kwargs)
def _deserialize(self, obj):
if isinstance(obj, dict):
return {
k: evaluate_jinja_expression(v, self.context)
if isinstance(v, str)
else v
for k, v in obj.items()
}
elif isinstance(obj, list):
return [
evaluate_jinja_expression(v, self.context) if isinstance(v, str) else v
for v in obj
]
return obj
json_string = """
{
"name": "John Doe",
"greeting": "Hello, {{ name | upper }}! today is {{ now() }}",
"age": 30,
"info": {
"description": "User is {{ age }} years old."
}
}
"""
context = {"name": "Jane Doe", "age": 25}
deserialized_data = json.loads(json_string, cls=JinjaJSONDecoder, context=context)
assert deserialized_data == {
"name": "John Doe",
"greeting": "Hello, JANE DOE! today is 2024-01-01 01:01:01",
"age": 30,
"info": {
"description": "User is 25 years old."
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment