diff --git a/benedict/serializers/csv.py b/benedict/serializers/csv.py index af59bd03..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]): - 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) + 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 e29346d9..c0093970 100644 --- a/tests/dicts/io/test_io_dict_csv.py +++ b/tests/dicts/io/test_io_dict_csv.py @@ -181,6 +181,27 @@ 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] + ) + + # 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( {