From 360d80cb0475a20f307612741eb3dbf28ec6cfef Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 11 Oct 2026 04:11:34 +0800 Subject: [PATCH 1/2] Keep original keys when deriving CSV columns --- benedict/serializers/csv.py | 4 ++-- tests/dicts/io/test_io_dict_csv.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/benedict/serializers/csv.py b/benedict/serializers/csv.py index af59bd03..ad377051 100644 --- a/benedict/serializers/csv.py +++ b/benedict/serializers/csv.py @@ -51,8 +51,8 @@ def encode(self, d: list[dict[str, Any]], **kwargs: Any) -> str: columns = kwargs.pop("columns", None) columns_row = kwargs.pop("columns_row", True) if not columns and len(ls) and type_util.is_dict(ls[0]): - keys = [str(key) for key in ls[0].keys()] - columns = sorted(keys) + # 2026-10-11: Keep original keys for lookup while sorting their labels. + columns = sorted(ls[0].keys(), key=str) f = StringIO() w = csv.writer(f, **kwargs) if columns_row and columns: diff --git a/tests/dicts/io/test_io_dict_csv.py b/tests/dicts/io/test_io_dict_csv.py index e29346d9..371507b5 100644 --- a/tests/dicts/io/test_io_dict_csv.py +++ b/tests/dicts/io/test_io_dict_csv.py @@ -181,6 +181,20 @@ def test_to_csv(self) -> None: """ self.assertEqual(s, r) + # 2026-10-11: Column labels must not replace the keys used for row lookup. + def test_to_csv_with_non_string_keys(self) -> None: + for row, expected in ( + ({2: "second", 1: "first"}, "1,2\nfirst,second\n"), + ({2: "second", "a": "first"}, "2,a\nsecond,first\n"), + ({2: "second", "10": "first"}, "10,2\nfirst,second\n"), + ): + with self.subTest(row=row): + d = IODict({"values": [row]}) + self.assertEqual(d.to_csv(), expected) + self.assertEqual( + d.to_csv(columns_row=False), expected.split("\n", 1)[1] + ) + def test_to_csv_with_custom_columns(self) -> None: d = IODict( { From 686eb62f021daa02a7c9d4b0139ee0885fce3de8 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 11 Oct 2026 04:21:04 +0800 Subject: [PATCH 2/2] Retain inferred CSV labels for None keys --- benedict/serializers/csv.py | 5 ++++- tests/dicts/io/test_io_dict_csv.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/benedict/serializers/csv.py b/benedict/serializers/csv.py index ad377051..ec83d8f6 100644 --- a/benedict/serializers/csv.py +++ b/benedict/serializers/csv.py @@ -50,13 +50,16 @@ def encode(self, d: list[dict[str, Any]], **kwargs: Any) -> str: kwargs.setdefault("lineterminator", "\n") columns = kwargs.pop("columns", None) columns_row = kwargs.pop("columns_row", True) + # 2026-10-11: Supplied headers keep their existing writer behavior. + headers = columns if not columns and len(ls) and type_util.is_dict(ls[0]): # 2026-10-11: Keep original keys for lookup while sorting their labels. columns = sorted(ls[0].keys(), key=str) + headers = [str(key) for key in columns] f = StringIO() w = csv.writer(f, **kwargs) if columns_row and columns: - w.writerow(columns) + w.writerow(headers) for item in ls: if type_util.is_dict(item): row = [item.get(key, "") for key in columns] diff --git a/tests/dicts/io/test_io_dict_csv.py b/tests/dicts/io/test_io_dict_csv.py index 371507b5..c0093970 100644 --- a/tests/dicts/io/test_io_dict_csv.py +++ b/tests/dicts/io/test_io_dict_csv.py @@ -195,6 +195,13 @@ def test_to_csv_with_non_string_keys(self) -> None: d.to_csv(columns_row=False), expected.split("\n", 1)[1] ) + # 2026-10-11: Inferred labels stringify None while explicit columns keep it. + def test_to_csv_with_none_key(self) -> None: + d = IODict({"values": [{None: "first", "a": "second"}]}) + self.assertEqual(d.to_csv(), "None,a\nfirst,second\n") + self.assertEqual(d.to_csv(columns_row=False), "first,second\n") + self.assertEqual(d.to_csv(columns=[None, "a"]), ",a\nfirst,second\n") + def test_to_csv_with_custom_columns(self) -> None: d = IODict( {