Skip to content

A BOM-prefixed JSON document is reported as JSON, where file reports Unicode text #3500

Closed

Description

@ESultanik

Summary

PolyFile reports a UTF-8 BOM followed by a JSON document as JSON. file does not, because its JSON
parser works on bytes and the BOM is not valid JSON syntax.

Reproducer

$ printf '\xef\xbb\xbf{}' > bom.json
$ TZ=UTC ./file/src/file -b -m file/magic/magic.mgc bom.json
Unicode text, UTF-8 (with BOM) text, with no line terminators
$ polyfile bom.json
JSON text data

Cause

PolyFile's JSON test decodes the buffer with Python's json machinery, which strips a UTF-8 BOM as
part of its encoding detection, so the BOM never reaches the parser. libmagic's file_is_json
(file/src/is_json.c:421-455) advances a byte pointer and rejects the first byte outright.

This predates #3486 and survives it: the newline-delimited JSON work replaced the whole-buffer
json.loads with a raw_decode loop but kept Python's encoding detection, so the divergence is
unchanged.

Worth deciding, not just fixing

Which behavior is correct here is a judgment call, so decide it before changing anything:

  • RFC 8259 section 8.1 says a JSON implementation must not add a BOM and may ignore one when
    parsing. Both tools are within spec.
  • PolyFile's default output mimics file, which argues for matching it.
  • Against that, a BOM-prefixed JSON file is JSON in practice, and several editors write one. Calling
    it plain Unicode text is arguably less useful, even if it is what file does.

Whichever way it goes, the behavior deserves a test, since right now it is incidental to a library's
encoding detection rather than chosen.

Impact

One class of input reports a different type from file. No corpus stem covers it. Found while
verifying #3486.

Activity

  1. ESultanik commented on Sep 10, 2026

    @ESultanik
    CollaboratorAuthor

    This is broader than a BOM. Python's json encoding detection also handles UTF-16 and UTF-32, so
    PolyFile reports JSON for inputs libmagic does not consider JSON at all.

    $ python3 -c "import pathlib; pathlib.Path('u16.json').write_bytes('{\"a\": 1}\n'.encode('utf-16-le'))"
    $ TZ=UTC ./file/src/file -b -k -r -m file/magic/magic.mgc u16.json
    Unicode text, UTF-16, little-endian text
    $ polyfile u16.json
    JSON text data

    libmagic's file_is_json walks raw bytes, so UTF-16-encoded JSON is simply not JSON to it — the
    same reason a UTF-8 BOM disqualifies a document there. parse_json decodes with
    json.detect_encoding first, which accepts all of UTF-8-with-BOM, UTF-16 and UTF-32.

    So the decision this issue asks for is one decision, not two: whether PolyFile's JSON test should
    work on bytes the way libmagic's does, or keep Python's encoding detection and diverge for every
    encoding it recognizes. Worth settling once rather than per-encoding.

    Confirmed pre-existing rather than introduced by recent work: master and the #3489 branch both
    report JSON text data for the file above, and #3489's precedes_soft_magic change deliberately
    keeps the JSON and CSV checks on the file's own bytes, so the decoded text buffer is not involved.

  2. self-assigned this
    on Sep 11, 2026
  3. modified the milestones: v0.1.6, v0.6.0 on Sep 11, 2026
  4. ESultanik commented on Sep 11, 2026

    @ESultanik
    CollaboratorAuthor

    Decision: report both

    This issue asked for the behavior to be decided before anything changed. Decided: emit the JSON
    match and let the text-encoding machinery also report Unicode text.
    Neither answer is lost.

    The reasoning is that a BOM-prefixed JSON file is JSON in practice — several editors write one — and
    PolyFile's model is to report every match rather than pick a winner. Suppressing the JSON match to
    mirror file would throw away the more useful of the two facts. RFC 8259 §8.1 permits either
    reading, so both tools stay in spec.

    This does diverge from file, which reports only Unicode text, UTF-8 (with BOM) text. That is a
    deliberate difference, not a defect, and the test should say so.

    Fix site

    parse_json at polyfile/magic.py:3700-3737. The single line is :3723:

    text = raw.decode(json.detect_encoding(raw), "surrogatepass")

    json.detect_encoding is what strips the BOM before the parser ever sees it. The docstring bullet
    list at :3707-3715 documents libmagic's rules and wants a third bullet recording this decision.

    JSONTest is at :3739-3784 (test at :3751-3763, precedes_soft_magic at :3768-3779);
    NDJSONTest at :3787-3790. Check whether precedes_soft_magic and appends_text_encoding need
    adjusting so the text-encoding description actually reaches the output alongside the JSON match —
    that is the part that makes "report both" real rather than nominal.

    Verification

    Add the test at around tests/test_magic.py:429, beside the existing JSON cases
    (test_newline_delimited_json :381, test_single_json_value :391,
    test_json_values_must_share_a_first_byte :409, test_bare_json_scalar_is_not_json :420).
    Pin all three of: the JSON match is present, the Unicode text description is present, and what
    file reports for the same bytes.

    Cover UTF-8, UTF-16 and UTF-32 BOMs — json.detect_encoding handles all of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions