Repository navigation
objects: the object graph as JSON, for a file that will not open anywhere else - #51
Merged
Merged
Conversation
…here else
BentoPDF's pdf-to-json. It writes what the file is MADE OF rather than what it
says: the trailer, and every object reachable from it.
The graph REACHABLE FROM THE TRAILER, not every object in the file. That is
the honest set because it is the one a reader sees: an object no reference
points at is in the bytes and in nobody's document.
A stream's bytes are not in it and its length is -- they are the bulk of most
files and they are binary, so including them would mean base64, and a 35 MB
file becoming a 47 MB JSON nobody reads.
A name keeps its slash and a string is WRAPPED, {"text":…} or {"hex":…},
because the two have to be told apart and a PDF string may be exactly
"/Catalog". Through reader.TextString, since the bytes of a text string are
not text. A reference that could not be read is NAMED in `unread` rather than
dropped.
TWO GUARDS THAT MASKED EACH OTHER, found by a surviving mutation. A PDF is a
graph -- the page tree's Kids point at the pages and every page's Parent
points back -- so an object is marked seen BEFORE it is read. Remove that and
the walk should never end; it ended anyway, because the depth guard cut it off
at 256 and produced a finite, enormous, wrong answer. "It terminated" cannot
tell the two apart. Counting the reads can, and the test now asserts ONE READ
PER OBJECT.
And a sort that did nothing: encoding/json sorts a map's keys itself, so
sorting them here only looked like the thing making the output deterministic.
A mutation reversing it changed no test. Removed, with two runs of the real
file compared byte for byte to show determinism survived it.
The walker holds a GETTER rather than the document, which is the seam that
makes the unreadable-reference branch testable: reader.Get is forgiving by
design -- a reference to an object never written resolves to Null, not an
error -- so no file reaches it.
100% of statements with no uncovered blocks, 14 mutations caught. One dead
`case nil` removed: reader says in its own words that a missing entry
resolves to Null "so callers never have to nil-check".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BentoPDF's
pdf-to-json. It writes what the file is made of rather thanwhat it says.
⛔ The graph reachable from the trailer, not every object in the file. That
is the honest set because it is the one a reader sees: an object no reference
points at is in the bytes and in nobody's document.
⛔ A stream's bytes are not in it, its length and dictionary are. They are
the bulk of most files and they are binary, so including them would mean
base64 — a 35 MB file becoming a 47 MB JSON nobody reads.
A name keeps its slash and a string is wrapped,
{"text": …}or{"hex": …}, because the two have to be told apart and a PDF string may beexactly
/Catalog. Throughreader.TextString, since the bytes of a textstring are not text. A reference that could not be read is named in
unreadrather than dropped.⛔ Two guards that masked each other
Found by a surviving mutation. A PDF is a graph — the page tree's
Kidspoint at the pages and every page's
Parentpoints back — so an object ismarked seen before it is read. Remove that and the walk should never end.
It ended anyway: the depth guard cut it off at 256 and produced a finite,
enormous, wrong answer. "It terminated" cannot tell the two apart. Counting
the reads can, and the test now asserts one read per object.
And a sort that did nothing
encoding/jsonsorts a map's keys itself, so sorting them here only lookedlike the thing making the output deterministic — a mutation reversing it
changed no test. Removed, with two runs of a real file compared byte for byte
to show determinism survived it.
The seam
The walker holds a getter rather than the document, which is what makes
the unreadable-reference branch testable:
reader.Getis forgiving by design— a reference to an object never written resolves to
Null, not an error — sono file reaches it.
How it is checked
100% of statements with no uncovered blocks, 14 mutations caught. One dead
case nilremoved:readersays in its own words that a missing entryresolves to
Null"so callers never have to nil-check".🤖 Generated with Claude Code