panzi-json-logic
A pure Python 3 implementation of JsonLogic and CertLogic for evaluating portable, JSON-encoded rules against data.
Repository Health
Technical Analysis
panzi-json-logic is a pure Python 3 implementation of the JsonLogic rule format, plus its CertLogic dialect used by the EU Digital COVID Certificate business-rules ecosystem. JsonLogic lets you express conditional logic — comparisons, boolean combinators, array operations like map/filter/reduce, string manipulation — as plain JSON objects, so the same rule can be authored once and shared between a front-end, a back-end, and a database record without re-implementing the logic in each language.
The library closely tracks the semantics of the reference json-logic-js implementation, including JavaScript’s loose-equality coercion behavior for the == operator, while also offering strict Python-native alternatives. It ships an extensible operations model: rather than mutating a shared global operator table, callers pass their own operations dictionary into jsonLogic(), making it straightforward to add custom operators or restrict which ones are available. A separate extras module adds non-standard but commonly needed operators such as now, parseTime, formatTime, timeSince, and combinations.
What You Get
- A
jsonLogic()function implementing the full JsonLogic operator set: comparisons, boolean logic, arithmetic, string operations, and array operators (map, filter, reduce, all, some, none) - A
certLogic()function implementing CertLogic, the dialect used for EU Digital COVID Certificate business rules, including date/time operators likeplusTime,before, andafter - A pluggable operations model — pass a custom dict of operators into
jsonLogic()/certLogic()instead of mutating a global registry - An
extrasmodule with non-standard operators (now,parseTime,formatTime,timeSince,hours,days,combinations,zip) for time-based and combinatorial rules - Both a strict, Python-native
===-style equality path and a JavaScript-compatible loose==path that mirrors json-logic-js coercion rules
Common Use Cases
- Storing and evaluating conditional business rules alongside a database record instead of hard-coding them in application code
- Sharing a single rule definition between a JavaScript front-end and a Python back-end without re-implementing the logic twice
- Implementing CertLogic-based eligibility or validity checks, such as digital-certificate business rules
- Building configurable feature-gating, pricing, or eligibility logic that non-developers can express as JSON rather than code
- Filtering, mapping, or reducing structured data using rule trees that can be authored, stored, and validated independently of the codebase
Under The Hood
Architecture
The library is a small, purely functional interpreter for a JSON-encoded expression tree. json_logic/apply.py defines a single recursive apply() function: it inspects the incoming logic value, and for a dict with exactly one key treats that key as the operator and its value as the argument list. Short-circuiting and context-sensitive operators — if/?:, and, or, map, filter, reduce, all, some, none — are handled as explicit branches inline in apply() because they need to control evaluation order or thread accumulator/current-item context to sub-rules, rather than eagerly evaluating all arguments up front like ordinary operators. Everything else is dispatched through a caller-supplied operations dict, resolved either by direct key lookup or, for namespaced operator names containing a dot, by walking nested dicts. json_logic/cert_logic/apply.py mirrors this same structure for the CertLogic dialect, with its own short-circuit set and builtins module. This design means the interpreter has no global mutable operator table — the operations dict is threaded explicitly through every recursive call, so composing custom operator sets or sandboxing available operations is a matter of constructing a different dict, not monkeypatching library internals.
Tech Stack
Pure Python 3 (python_requires >= 3.6) with zero runtime dependencies — the entire implementation totals a few hundred lines across json_logic/apply.py, json_logic/builtins.py, and the cert_logic sub-package. It uses only the standard library: datetime/time/wsgiref.handlers.format_date_time for the extras module’s time-based operators, and json for the __main__.py CLI entry point that lets rules be evaluated from the command line. Packaging is classic setuptools via setup.cfg plus a minimal PEP 517 pyproject.toml build-system declaration; there is no build step beyond that, and the package is distributed on PyPI as panzi-json-logic.
Code Quality
The project uses Python’s built-in unittest framework in a single tests.py, but the coverage is unusually thorough for its size: it loads and runs the official JsonLogic test suite (testdata/tests.json, ~298 cases) plus CertLogic’s own test suite (testdata/certlogic/*.json) and additional valid/invalid rule fixtures, dynamically generating a unittest.TestCase per fixture rather than hand-writing each assertion. Type hints are used throughout (JsonValue, Operations from json_logic/types.py), and operator functions follow a consistent op_<name>(data, *args) naming and signature convention. There is no CI configuration or linter config in the repository itself, but the reliance on upstream, community-maintained conformance test suites substitutes for a large hand-written test surface.
API Design
The public surface is deliberately minimal: jsonLogic(rule, data=None, operations=BUILTINS) and certLogic(rule, data=None, operations=BUILTINS), each a single function call with sensible defaults, so getting started requires no setup beyond pip install panzi-json-logic and an import. Extending behavior is explicit and functional — spread BUILTINS into a new dict with additional entries — rather than requiring subclassing or a registration API, which keeps the mental model small. The one documented ergonomic wrinkle is substr’s UTF-16 vs. code-point semantics diverging from the JavaScript reference implementation, which the README calls out directly along with an opt-in op_substr_utf16 shim for callers who need byte-for-byte JS parity.