fix(dataprep): skip .jsonl lines that are valid JSON but not objects (#7195)

* fix(dataprep): skip .jsonl lines that are valid JSON but not objects

`_read_file_by_format` json.loads each line and hands the result to
`_extract_text_from_json`, which assumes a dict:

    for field in self._TEXT_FIELDS:
        if field in data and isinstance(data[field], str):

A JSON line does not have to be an object -- `"context"`, `["text"]` and
`42` are all valid JSON. For those, `field in data` stops being a key
lookup and becomes a substring/membership test, so `data[field]` raises:

    "context"        -> "text" in "context" is True (substring!)
                     -> TypeError: string indices must be integers
    ["text", "foo"]  -> TypeError: list indices must be integers
    42               -> TypeError: argument of type 'int' is not iterable

The TypeError escapes past `except json.JSONDecodeError: continue`, so the
whole load dies on one odd line.

That except clause is also the tell: a *malformed* line is already skipped
gracefully. A *well-formed* line that happens not to be an object should be
too -- it carries no text either way. This makes the two agree.

Reachable from `unsloth-cli.py:253` (`--dataset foo.jsonl` auto-detect) and
`RawTextDataLoader` is exported from `unsloth/__init__.py`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Slim the non-object jsonl regression test and shorten the guard comment

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
Andrew Chen 2026-07-18 20:54:50 +08:00 committed by GitHub
commit e55d0e6c75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 22 additions and 0 deletions

View file

@ -295,7 +295,25 @@ def test_smart_chunk_text_single_chunk_no_eos_returns_plain_list():
return True
def test_load_from_file_skips_non_object_json_lines():
"""Non-object .jsonl lines (valid JSON, not dicts) are skipped, not fatal."""
# "context" contains "text", ["text"] holds it, 42 isn't iterable -- each
# would reach data[field] and raise TypeError without the isinstance guard.
with tempfile.NamedTemporaryFile("w", suffix = ".jsonl", delete = False) as f:
f.write('"context"\n["text", "x"]\n42\n{"text": "keep this"}\n')
path = f.name
try:
text = RawTextDataLoader(None)._read_file_by_format(path, "json_lines")
assert text == "keep this", text
finally:
os.unlink(path)
print("test_load_from_file_skips_non_object_json_lines passed")
return True
if __name__ == "__main__":
success = test_raw_text_loader()
success = test_smart_chunk_text_single_chunk_no_eos_returns_plain_list() and success
success = test_load_from_file_skips_non_object_json_lines() and success
sys.exit(0 if success else 1)

View file

@ -236,6 +236,10 @@ class RawTextDataLoader:
def _extract_text_from_json(self, data):
"""Extract text from JSON object using common field names."""
# Skip non-object lines (str/list/number): `field in data` would be a
# substring/membership test, not a key lookup, and `data[field]` raises.
if not isinstance(data, dict):
return ""
for field in self._TEXT_FIELDS:
if field in data and isinstance(data[field], str):
return data[field]