Opened 3 weeks ago

Last modified 6 days ago

#37442 assigned Bug

JSONL deserializer incorrectly treats Unicode line separators as record boundaries

Reported by: Patel Namraa Owned by: Patel Namraa
Component: Core (Serialization) Version: 5.2
Severity: Normal Keywords:
Cc: Patel Namraa Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The JSONL deserializer incorrectly uses str.splitlines() to split string and bytes input into records. splitlines() treats U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR), and U+0085 (NEXT LINE) as line boundaries, even though these characters can occur unescaped inside JSON strings.

The JSONL serializer uses ensure_ascii=False, so these characters can be emitted directly in serialized JSON. As a result, data produced by Django's JSONL serializer cannot always be deserialized again when a serialized value contains one of these characters.

For example:

from django.core import serializers
from myapp.models import Author

author = Author(pk=1, name="Jane\u2028Doe")
data = serializers.serialize("jsonl", [author])
list(serializers.deserialize("jsonl", data))

This raises DeserializationError because the record is split at U+2028 instead of only at the JSONL record delimiter.

The same behavior occurs with U+2029 and U+0085, and with both str and bytes input.

The JSONL serializer uses \n as the record separator, so the deserializer should split only on \n rather than using splitlines().

A possible fix is:

stream_or_string = stream_or_string.split("\n")

This continues to support CRLF input because the resulting line retains a trailing \r, which is valid JSON whitespace.

I have added regression tests covering U+2028, U+2029, and U+0085 with both str and bytes input, as well as a test confirming that CRLF line endings continue to work.

The issue does not affect loading JSONL from a file object in the same way, since file iteration uses the actual newline delimiter. However, it affects callers passing serialized JSONL as a string or bytes object to serializers.deserialize().

I have reproduced the issue against Django's current development version.

Change History (4)

comment:1 by David Smith, 3 weeks ago

Thanks for the report. Regression in ee5147cfd7de2add74a285537a8968ec074e70cd (Django 5.2)

diff --git a/tests/serializers/test_jsonl.py b/tests/serializers/test_jsonl.py
index 3137b037a9..98eb2edc15 100644
--- a/tests/serializers/test_jsonl.py
+++ b/tests/serializers/test_jsonl.py
@@ -8,7 +8,7 @@ from django.db import models
 from django.test import TestCase, TransactionTestCase
 from django.test.utils import isolate_apps
 
-from .models import Score
+from .models import Score, Category
 from .tests import SerializersTestBase, SerializersTransactionTestBase
 
 
@@ -99,6 +99,17 @@ class JsonlSerializerTestCase(SerializersTestBase, TestCase):
         ):
             list(serializers.deserialize("jsonl", test_string))
 
+    def test_unicode_line_separators(self):
+        for separator in ("\u0085", "\u2028", "\u2029"):
+            name = f"Jane{separator}Doe"
+            data = serializers.serialize("jsonl", [Category(pk=1, name=name)])
+            for value in (data, data.encode()):
+                with self.subTest(separator=repr(separator), input_type=type(value)):
+                    objects = list(serializers.deserialize("jsonl", value))
+                    self.assertEqual(len(objects), 1)
+                    self.assertEqual(objects[0].object.name, name)
+
+
     def test_helpful_error_message_invalid_field(self):
         """
         If there is an invalid field value, the error message contains the

comment:2 by David Smith, 3 weeks ago

Has patch: unset
Triage Stage: Unreviewed → Accepted
Version: dev → 6.1

comment:3 by Jacob Walls, 3 weeks ago

Version: 6.1 → 5.2

comment:4 by Patel Namraa, 6 days ago

Has patch: set
Note: See TracTickets for help on using tickets.
Back to Top