diff --git a/benedict/serializers/csv.py b/benedict/serializers/csv.py index af59bd03..eae1a08f 100644 --- a/benedict/serializers/csv.py +++ b/benedict/serializers/csv.py @@ -27,7 +27,8 @@ def decode(self, s: str, **kwargs: Any) -> list[dict[str, Any]]: kwargs.setdefault("quoting", csv.QUOTE_ALL) columns = kwargs.pop("columns", None) columns_row = kwargs.pop("columns_row", True) - f = StringIO(s) + # 2026-10-11: Let csv handle record endings and preserve quoted newlines. + f = StringIO(s, newline="") r = csv.reader(f, **kwargs) ln = 0 data = [] diff --git a/tests/serializers/test_csv_serializer.py b/tests/serializers/test_csv_serializer.py index 5b8bef14..1e97ecf7 100644 --- a/tests/serializers/test_csv_serializer.py +++ b/tests/serializers/test_csv_serializer.py @@ -12,6 +12,19 @@ def test_decode_csv(self) -> None: # TODO pass + # 2026-10-11: CSV readers accept CR, LF and CRLF without rewriting quoted data. + def test_decode_csv_newlines(self) -> None: + from benedict.serializers import CSVSerializer + + serializer = CSVSerializer() + for newline in ("\r", "\n", "\r\n"): + with self.subTest(newline=newline): + text = f'name,note{newline}Alice,"first\r\nsecond"{newline}' + self.assertEqual( + serializer.decode(text), + [{"name": "Alice", "note": "first\r\nsecond"}], + ) + def test_encode_csv(self) -> None: # TODO pass