From f42ad69366e8854186904df9bdd51c854e30c5ff Mon Sep 17 00:00:00 2001 From: Umut Alisoglu Date: Sun, 25 Oct 2026 22:13:37 +0100 Subject: [PATCH] Preserve empty-string parent keys when flattening --- benedict/core/flatten.py | 10 ++++++---- tests/core/test_flatten.py | 4 ++-- tests/dicts/test_benedict.py | 22 +++++++++++++++++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/benedict/core/flatten.py b/benedict/core/flatten.py index f27e16f8..ac6e08dd 100644 --- a/benedict/core/flatten.py +++ b/benedict/core/flatten.py @@ -1,11 +1,13 @@ +from __future__ import annotations + from collections.abc import Mapping from typing import Any from benedict.core.clone import clone -def _flatten_key(base_key: str, key: str, separator: str) -> str: - if base_key and separator: +def _flatten_key(base_key: str | None, key: str, separator: str) -> str: + if base_key is not None and separator: return f"{base_key}{separator}{key}" return key @@ -49,7 +51,7 @@ def _flatten_list( def _flatten_item( d: Any, base_dict: Any, - base_key: str, + base_key: str | None, separator: str, indexes: bool = False, ) -> Any: @@ -87,7 +89,7 @@ def flatten(d: Any, separator: str = "_", indexes: bool = False) -> Any: # collect items in a plain dict, flattened keys (eg. "a[0]") # must not be parsed as keypaths by benedict instances items = _flatten_item( - d, base_dict={}, base_key="", separator=separator, indexes=indexes + d, base_dict={}, base_key=None, separator=separator, indexes=indexes ) new_dict = clone(d, empty=True) new_dict.update(items) diff --git a/tests/core/test_flatten.py b/tests/core/test_flatten.py index 52c05005..2436c0a7 100644 --- a/tests/core/test_flatten.py +++ b/tests/core/test_flatten.py @@ -33,7 +33,7 @@ def test_flatten(self) -> None: self.assertEqual(o, r) def test_flatten_with_falsy_parent_keys(self) -> None: - for key in (0, False, None): + for key in (0, False, None, ""): for separator in ("_", "/"): for indexes in (False, True): with self.subTest(key=key, separator=separator, indexes=indexes): @@ -45,7 +45,7 @@ def test_flatten_with_falsy_parent_keys(self) -> None: self.assertEqual(data, {key: {"child": 1}, "child": 2}) def test_flatten_with_falsy_parent_keys_and_indexed_lists(self) -> None: - for key in (0, False, None): + for key in (0, False, None, ""): with self.subTest(key=key): self.assertEqual( _flatten({key: {"items": [{"value": 1}]}}, indexes=True), diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index c2db8375..b38a42a0 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -524,7 +524,7 @@ def test_flatten(self) -> None: self.assertTrue(isinstance(f, benedict)) def test_flatten_with_falsy_parent_keys(self) -> None: - for key in (0, False, None): + for key in (0, False, None, ""): with self.subTest(key=key): source = benedict({key: {"child": 1}, "child": 2}) result = source.flatten() @@ -532,6 +532,26 @@ def test_flatten_with_falsy_parent_keys(self) -> None: self.assertIsInstance(result, benedict) self.assertEqual(source, {key: {"child": 1}, "child": 2}) + def test_flatten_empty_parent_keys_round_trip(self) -> None: + for separator in ("_", "/"): + with self.subTest(separator=separator): + data = {"": {"": 1, "items": [{"value": 2}]}, "value": 3} + source = benedict(data) + result = source.flatten(separator=separator, indexes=True) + self.assertEqual( + result, + { + separator: 1, + f"{separator}items[0]{separator}value": 2, + "value": 3, + }, + ) + self.assertIsInstance(result, benedict) + self.assertEqual(result.unflatten(separator=separator), data) + self.assertEqual( + data, {"": {"": 1, "items": [{"value": 2}]}, "value": 3} + ) + def test_flatten_with_custom_keypath_separator(self) -> None: d = { "a": 1,