Skip to content

KuchikiRenji/jmesflat

Repository files navigation

jmesflat — Flatten, Unflatten & Merge Nested JSON in Python

Python 3.10+ License: MIT Coverage

jmesflat is a Python library that flattens, unflattens, and merges deeply nested JSON objects using JMESPath-style keys (e.g. a.b[0].c). Build and combine complex nested structures without manual layer-by-layer construction.


What is jmesflat?

jmesflat extends jmespath with four main operations:

Operation Description
flatten Turn nested dicts/lists into flat key–value pairs with dotted and bracket notation.
unflatten Rebuild nested objects from flat keys (e.g. build {"a": {"b": [{"c": 1}]}} from {"a.b[0].c": 1}).
merge Deep-merge two nested structures with configurable array behavior (overwrite, extend top/bottom, deduped).
clean Recursively filter nested data with a custom discard function.

Use it when you need to flatten JSON for storage or APIs, build nested objects from flat configs, or merge nested JSON without hand-written recursion.


Quick start

>>> import jmesflat as jf

>>> # Build nested object from flat keys
>>> jf.unflatten({"a.b[0].c": "x", "a.b[1].d": "y"})
{'a': {'b': [{'c': 'x'}, {'d': 'y'}]}}

>>> # Flatten for compact, readable output
>>> jf.flatten({"a": {"b": [{"c": "x"}, {"d": "y"}]})
{'a.b[0].c': 'x', 'a.b[1].d': 'y'}

>>> # Deep-merge two nested objects
>>> jf.merge({"a": {"x": 1}}, {"a": {"y": 2}})
{'a': {'x': 1, 'y': 2}}

Installation

pip install jmesflat

Requirements: Python 3.10+, jmespath.


Table of contents


Key features

  • JMESPath-style keys — Dotted paths and bracket indices: a.b[0].c, "Outer Key.inner".
  • Keys with spaces and special chars — Supports keys containing spaces, @, and -.
  • Arbitrary nesting — Mixed and multi-level arrays and objects.
  • Depth control — Optional level parameter to flatten, unflatten, or merge up to a given object depth.
  • Array merge modesoverwrite, topdown, bottomup, or deduped when merging.
  • Filter while transforming — Use discard_check in flatten/unflatten/merge, or use clean() alone to scrub nested data.
  • Atomic types — Empty lists [] and dicts {} are treated as atomic values in flattened output.

Common use cases

1. Building nested objects from flat key–value pairs

>>> import jmesflat as jf
>>> nest = jf.unflatten({"a.b[0].c[0].d": "e", "a.b[1].f": "g"})
>>> nest
{'a': {'b': [{'c': [{'d': 'e'}]}, {'f': 'g'}]}}

2. Merging deeply nested objects

>>> nest1 = {"a": {"b": [{"c": [{"d": "e"}]}, {"f": "g"}]}}
>>> nest2 = {"a": {"b": [{"f": "g"}, {"c": [{"d": "e"}]}]}}
>>> jf.merge(nest1, nest2)
{'a': {'b': [{'c': [{'d': 'e'}], 'f': 'g'}, {'c': [{'d': 'e'}], 'f': 'g'}]}}

3. Compact, human-readable dumps of nested data

>>> import json
>>> nested = {"a": {"b": [{"c": "x"}, {"d": "y"}]}}
>>> print(json.dumps(jf.flatten(nested), indent=2))
{
  "a.b[0].c": "x",
  "a.b[1].d": "y"
}

4. Flatten at a specific depth (level)

>>> import json
>>> import jmesflat as jf
>>>
>>> data = {
...     "Outer Key 1": {
...         "arr": ["a", {"nested": "val"}, 123],
...     },
...     "Outer Key 2": {"deep": {"a": [{"b": 1}, {"c": 2}]}},
... }
>>> flat = jf.flatten(data, level=1)
>>> print(json.dumps(flat, indent=2))
{
  "Outer Key 1": {
    "arr[0]": "a",
    "arr[1].nested": "val",
    "arr[2]": 123
  },
  "Outer Key 2": {
    "deep.a[0].b": 1,
    "deep.a[1].c": 2
  }
}
>>> jf.unflatten(flat, level=1) == data
True

5. Merge strategies and array handling

  • overwrite (default) — Second structure overwrites at the first array.
  • topdown — Extend arrays at the first array level.
  • bottomup — Extend at the innermost array level.
  • deduped — Merge arrays with custom matching to avoid duplicates.

Use the array_merge parameter of merge():

>>> jf.merge(nest1, nest2, array_merge="topdown")   # extend at first array
>>> jf.merge(nest1, nest2, array_merge="bottomup")  # extend at last array

6. Cleaning / filtering nested data

>>> # Keep only string and int values, drop keys containing "-"
>>> cleaned = jf.clean(
...     data,
...     discard_check=lambda key, val: "-" in key or not isinstance(val, (str, int)),
... )

Configuration via constants module

Set global defaults via jmesflat.constants:

  • DISCARD_CHECK — Default filter for clean() and for the second object in merge().
    Example: drop all None values:
    import jmesflat as jf
    jf.constants.DISCARD_CHECK = lambda _, val: val is None
  • MISSING_ARRAY_ENTRY_VALUE — Callable used when unflattening to pad array indices.
    It receives the flattened key and the value being set and returns the padding value.

Other constants in the module are for advanced use and may change in future versions.


Test coverage

Module Statements Missing Coverage
jmesflat/init.py 7 0 100%
jmesflat/_clean.py 11 0 100%
jmesflat/_flatten.py 39 0 100%
jmesflat/_merge.py 41 0 100%
jmesflat/_unflatten.py 50 0 100%
jmesflat/constants.py 8 0 100%
jmesflat/utils.py 29 0 100%
Total 185 0 100%

Run tests:

pip install pytest pytest-cov
pytest tests/ --cov=jmesflat --cov-report=term-missing

License

MIT

About

jmesflat: Python library to flatten, unflatten and merge nested JSON with JMESPath-style keys. Use for configs, APIs, and deep merges.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors