Skip to main content

jmesflat ✨

Python 3.10+ Coverage

Built upon and considered an extension of jmespath, jmesflat is similarly pronounced (say "James flat") and provides a simple interface for flattening, 'unflattening', and merging deeply nested JSON objects.

Common use cases:

>>> # 1. Building deeply nested objects without constructing individual layers:
>>> import jmesflat as jf
>>> nest1 = jf.unflatten({"a.b[0].c[0].d": "e", "a.b[1].f": "g"})
>>> nest1
{'a': {'b': [{'c': [{'d': 'e'}]}, {'f': 'g'}]}}
>>>
>>> # 2. Merging deeply nested objects:
>>> nest2 = {"a": {"b": [{"f": "g"}, {"c": [{"d": "e"}]}]}}
>>> merged_nest = jf.merge(nest1, nest2)
>>> merged_nest
{'a': {'b': [{'c': [{'d': 'e'}], 'f': 'g'}, {'c': [{'d': 'e'}], 'f': 'g'}]}}
>>>
>>> # 3. Making dumps of complex nest objects compact and human readable
>>> import json
>>> print(json.dumps(jf.flatten(merged_nest), indent=2))
{
  "a.b[0].c[0].d": "e",
  "a.b[0].f": "g",
  "a.b[1].c[0].d": "e",
  "a.b[1].f": "g"
}

🚀 Installation

pip install jmesflat

📋 Requirements

  • Python 3.10+
  • jmespath

🎯 Key Features

  1. Keys can contain spaces and reserved characters (@ and -)
  2. Supports any arbitrary nesting pattern including mixed and multi-level array objects
  3. Empty lists / dicts are considered atomic types and included in the flattened output alongside the 'true' atomic types int, float, str, bool, and None
  4. Flatten / unflatten / merge at an arbitrary object depth using the level parameter (depth cannot exceed the depth of the first array instance)
  5. Extend rather than overwrite arrays during merge operations using the array_merge parameter. 'topdown' merges extend at the first array instance. 'bottomup' extends at the final array instance.
  6. Scrub the data during flatten/unflatten/merge operations or simply scrub a nested object via clean using the discard_check parameter. The check is ONLY applied to nest2 during the merge operation.
>>> import json
>>> import jmesflat as jf
>>>
>>> test_nest = {
...     "Outer Object Key 1": {
...         "mixedArray": [
...             "mixed array string",
...             {"mixed Array Object 1 Key": "spaces demo"},
...             12345,
...             [
...                 {"@subArray": "@ symbol demo"},
...                 {"@subArray": 1.2345},
...                 {"@subArray": None},
...             ],
...             {"mixed-array-object-2-key": "dashed key demo"},
...             [],
...             {},
...         ],
...     },
...     "Outer Object Key 2": {
...         "deepNest": {
...             "a": [
...                 {"b": 1},
...                 {
...                     "c": {
...                         "d": [
...                             {"e": "f", "g": "h"},
...                             {"e": "f1"}
...                         ]
...                     }
...                 }
...             ]
...         },
...     },
... }
>>>
>>> flat = jf.flatten(test_nest, level=1)
>>> print(json.dumps(flat, indent=2))
{
  "Outer Object Key 1": {
    "mixedArray[0]": "mixed array string",
    "mixedArray[1].mixed Array Object 1 Key": "spaces demo",
    "mixedArray[2]": 12345,
    "mixedArray[3][0].@subArray": "@ symbol demo",
    "mixedArray[3][1].@subArray": "@ symbol demo",
    "mixedArray[3][2].@subArray": "@ symbol demo",
    "mixedArray[4].mixed-array-object-2-key": "dashed key demo",
    "mixedArray[5]": [],
    "mixedArray[6]": {}
  },
  "Outer Object Key 2": {
    "deepNest.a[0].b": 1,
    "deepNest.a[1].c.d[0].e": "f",
    "deepNest.a[1].c.d[0].g": "h",
    "deepNest.a[1].c.d[1].e": "f1"
  }
}
>>>
>>> jf.unflatten(flat, level=1) == test_nest
True
>>>
>>> from copy import deepcopy
>>> test_nest2 = deepcopy(test_nest)
>>> # NOTE: `jf.flatten` wrapper is used for ease of visualization only in the merge/clean examples below
>>> print(json.dumps(jf.flatten(jf.merge(test_nest, test_nest2, level=1), level=2), indent=2))
{
  "Outer Object Key 1": {
    "mixedArray": {
      "[0]": "mixed array string",
      "[1].mixed Array Object 1 Key": "spaces demo",
      "[2]": 12345,
      "[3][0].@subArray": "@ symbol demo",
      "[3][1].@subArray": 1.2345,
      "[3][2].@subArray": null,
      "[4].mixed-array-object-2-key": "dashed key demo",
      "[5]": [],
      "[6]": {}
    }
  },
  "Outer Object Key 2": {
    "deepNest": {
      "a[0].b": 1,
      "a[1].c.d[0].e": "f",
      "a[1].c.d[0].g": "h",
      "a[1].c.d[1].e": "f1"
    }
  }
}
>>> print(json.dumps(jf.flatten(jf.merge(test_nest, test_nest2, level=1, array_merge="topdown"), level=2), indent=2))
{
  "Outer Object Key 1": {
    "mixedArray": {
      "[0]": "mixed array string",
      "[1].mixed Array Object 1 Key": "spaces demo",
      "[2]": 12345,
      "[3][0].@subArray": "@ symbol demo",
      "[3][1].@subArray": 1.2345,
      "[3][2].@subArray": null,
      "[4].mixed-array-object-2-key": "dashed key demo",
      "[5]": [],
      "[6]": {},
      "[7]": "mixed array string",
      "[8].mixed Array Object 1 Key": "spaces demo",
      "[9]": 12345,
      "[10][0].@subArray": "@ symbol demo",
      "[10][1].@subArray": 1.2345,
      "[10][2].@subArray": null,
      "[11].mixed-array-object-2-key": "dashed key demo",
      "[12]": [],
      "[13]": {}
    }
  },
  "Outer Object Key 2": {
    "deepNest": {
      "a[0].b": 1,
      "a[1].c.d[0].e": "f",
      "a[1].c.d[0].g": "h",
      "a[1].c.d[1].e": "f1",
      "a[2].b": 1,
      "a[3].c.d[0].e": "f",
      "a[3].c.d[0].g": "h",
      "a[3].c.d[1].e": "f1"
    }
  }
}
>>> print(json.dumps(jf.flatten(jf.merge(test_nest, test_nest2, level=1, array_merge="bottomup"), level=2), indent=2))
{
  "Outer Object Key 1": {
    "mixedArray": {
      "[0]": "mixed array string",
      "[1].mixed Array Object 1 Key": "spaces demo",
      "[2]": 12345,
      "[3][0].@subArray": "@ symbol demo",
      "[3][1].@subArray": 1.2345,
      "[3][2].@subArray": null,
      "[3][3].@subArray": "@ symbol demo",
      "[3][4].@subArray": 1.2345,
      "[3][5].@subArray": null,
      "[4].mixed-array-object-2-key": "dashed key demo",
      "[5]": [],
      "[6]": {},
      "[7]": "mixed array string",
      "[8].mixed Array Object 1 Key": "spaces demo",
      "[9]": 12345,
      "[10].mixed-array-object-2-key": "dashed key demo",
      "[11]": [],
      "[12]": {}
    }
  },
  "Outer Object Key 2": {
    "deepNest": {
      "a[0].b": 1,
      "a[1].c.d[0].e": "f",
      "a[1].c.d[0].g": "h",
      "a[1].c.d[1].e": "f1",
      "a[1].c.d[2].e": "f",
      "a[1].c.d[2].g": "h",
      "a[1].c.d[3].e": "f1",
      "a[2].b": 1
    }
  }
}
>>> print(json.dumps(jf.flatten(jf.clean(test_nest, discard_check=lambda key, val: "-" in key or not isinstance(val, (str, int))), level=2), indent=2))
{
  "Outer Object Key 1": {
    "mixedArray": {
      "[0]": "mixed array string",
      "[1].mixed Array Object 1 Key": "spaces demo",
      "[2]": 12345,
      "[3][0].@subArray": "@ symbol demo"
    }
  },
  "Outer Object Key 2": {
    "deepNest": {
      "a[0].b": 1,
      "a[1].c.d[0].e": "f",
      "a[1].c.d[0].g": "h",
      "a[1].c.d[1].e": "f1"
    }
  }
}

⚙️ Configuration via constants Module

The constants module allows global defaults to be set for several key features. For example, a global discard check function can be defined by setting the value of jf.constants.DISCARD_CHECK. If the user wishes to discard all None type values, simply set jf.constants.DISCARD_CHECK = lambda _, val: val is None after the initial import jmesflat as jf statement. In addition, users can customize the default values that will be used when extending arrays during an index preserving unflatten operation via jf.MISSING_ARRAY_ENTRY_VALUE, a callable that accepts the flattened key of the array element being set and the value said is being set to and returns the value that should be used to pad the array until its length is >= the desired index. Other settings in the constants module are considered 'use at your own risk' and included for possible future extensibility.

📊 Test Coverage

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jmesflat-0.2.1.tar.gz (23.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jmesflat-0.2.1-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file jmesflat-0.2.1.tar.gz.

File metadata

  • Download URL: jmesflat-0.2.1.tar.gz
  • Upload date:
  • Size: 23.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.3

File hashes

Hashes for jmesflat-0.2.1.tar.gz
Algorithm Hash digest
SHA256 370dcf0d841202d49b72d2f03c5dcd7a89daf661c64e9b03858bcf259aaaa651
MD5 8fb8bcfa6965fde5a114d568941e71c7
BLAKE2b-256 9c4702663b5e3a5855638a0e9aae2f9fe1a3975762201ceea8325f2aa6f1f16f

See more details on using hashes here.

File details

Details for the file jmesflat-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: jmesflat-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.3

File hashes

Hashes for jmesflat-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f02863270b2b38244dc539a3b43194c1b286b2e8fce45f3a66deb461abdcfeee
MD5 d49f56b98a0a4ae7d31ff3fbeb8cb0c5
BLAKE2b-256 138b833e2a97ec1355c7cc35fa6f033c4fc67f3f11f93e29c0068b52992c4f24

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page