simdjson_dart 1.2.2
simdjson_dart: ^1.2.2 copied to clipboard
Read a few fields out of a large JSON payload without decoding the rest, backed by the simdjson C++ library over FFI. Whole-document and NDJSON decoding too.
1.2.2 #
- Correct what the standalone-binaries section says about the SDK. It
called the
dart compile exerefusal a gap "tracked upstream at dart-lang/sdk#62593", which sends a reader off to wait for a fix that is not coming. #62593 is narrower than that: it asks for the check to be relaxed for projects that merely depend on a build hook they never invoke. On it, dcharkes writes that turning the error into a warning "will break the user experience for people who should switch todart build cli" — sodart build cliis the destination, not a stopgap. None of the seven opendart-cli-buildissues proposes teachingdart compileto run hooks. The section now says that, and still cites #62593 for what it actually covers. Docs only.
1.2.1 #
- Ship the README section on standalone binaries, which 1.2.0 left out. It
documents that
dart compile exerefuses a package with build hooks and thatdart buildis the way through — the thing that stops this package's own audience, people building a CLI or a server binary, from shipping. It was committed alongside 1.2.0 but not in the archive that was published, and pub.dev renders the README from the archive, so the page did not show it.
1.2.0 #
-
Add
SimdJsonDocument.openFile, which reads a file without it passing through Dart. The package is for pulling a few fields out of a payload large enough that decoding all of it is the wrong trade — and until now the only way in wasparseBytes, which needs the caller to hold the whole file as aUint8Listbefore anything is parsed, and then copies it again into the padded buffer simdjson works on. Two copies of the thing the package exists to avoid touching, and both grow with the file.openFiletakes a path and lets simdjson read into that buffer directly. Measured on a 4.7 MB export, opening it and reading one pointer: 1.3 ms against 2.5 ms throughparseBytes, with the intermediate list never built. It is a read rather than a memory map, so the bytes are paid for once and the file may change afterwards.A file that cannot be read and a file that is not JSON fail differently —
IO_ERRORagainst a parse error — so a caller can tell a path to fix from data to fix.
1.1.2 #
- Lead with what this package is actually for. The description and the README
opened on "fast JSON", which is the crowded claim and the weaker one: below
about 100 KB
dart:convertdecodes a whole document faster than this can, FFI boundary included, and the README already said so further down. The one thing the built-in cannot do is skip — read three fields out of a 9 MB response and leave the rest as bytes. That is where the 5-14x is, so it is what the first sentence now says. No API or behaviour change.
1.1.1 #
- Document what a rejection costs, with
bench/reject.dartbehind it. A document that is invalid at its second byte is not rejected in constant time: the input is encoded and copied into native memory before simdjson looks at it, so at 4 MB a rejection costs 13 ms throughsimdJsonDecodeand 0.9 ms throughsimdJsonDecodeBytes. That matters for a caller sitting in front of mixed input, which had no way to know it from the docs. No behaviour change.
1.1.0 #
-
Match
jsonDecodeon numbers simdjson will not represent. An integer pastuint64or an exponent that overflows to infinity is well-formed JSON thatdart:convertaccepts, and this package rejected it with aFormatException— a real gap for something whose pitch is returning the same shapes.simdJsonDecode,simdJsonDecodeBytesand both NDJSON entry points now hand the document tojsonDecodewhen simdjson failed only for a number's range (NUMBER_ERRORorBIGINT_ERROR, nothing wider), so{"v": 18446744073709551616}gives a double and{"v": 1e400}givesInfinity, both equal tojsonDecode. NDJSON retries line by line.The fix is on the Dart side on purpose. The C++ route —
number_as_stringplus aBIGINTcase — is wrong at its own boundary: simdjson returnsINVALID_NUMBERrather thanBIGINT_ERRORfor 20-digit positive overflows, so the whole window[2^64, 10^20)never becomes a big integer, andparse_manynever receives the flag at all, which would leave the two entry points disagreeing. That asymmetry is filed upstream as simdjson/simdjson#2791.Cost is a second parse, and only for documents that previously threw; the check sits inside the existing error branch, so nothing changes when simdjson succeeds. Validation is not loosened: malformed input that fails for any other reason still throws, which the tests pin.
SimdJsonDocument, the lazy reader, is unchanged and still throws — it returns a handle rather than a decoded value, so there is nothing to fall back to. Closes #3, reported via #1 by @arrrrny.
1.0.1 #
- Format the native-safety test added in 1.0.0.
test/ships in the archive and pana scores its formatting, so the unformatted file would have cost points. No code or API change.
1.0.0 #
The API is stable. No behaviour changes; this freezes the surface after an adversarial pass over the part that matters for a package holding native memory, and pins what it found as tests.
Verified by execution and now covered by test/native_safety_test.dart:
- Using a closed document throws a
StateErrorrather than reading freed memory, and closing twice is safe. - Malformed input (empty, truncated, an unterminated string, a bad literal,
runaway nesting) raises
FormatException; none of it crashes the process. - Parsing and closing 3,000 documents grows RSS by a few megabytes, not the hundreds a leak would cost.
- A document that is never closed is still reclaimed: growth over repeated batches builds up and then drops to under a megabyte once the GC runs the finalizer, which is what distinguishes a finalizer from a leak.
One honest caveat: the build hooks depend on native_toolchain_c, which is
pre-1.0, so a breaking release there may need a new build of this package. That
is a build-time dependency and does not reach the public API — the surface
frozen here is the Dart one.
0.2.6 #
- Add
example/README.mdfor pub.dev's Example tab (it was empty). It walks through the selective-access example — read a few fields from a 3 MB payload without decoding the rest, then the full decode and the error path — with the real output. Docs only.
0.2.5 #
- Declare
platforms: {linux, macos, windows}inpubspec.yaml. The build hook only ever runsCBuilderfor the host toolchain and has no Android/iOS support today; pub.dev had inferred support for all five platforms from static analysis alone with no declaration to override it.
0.2.4 #
- Publish the crossover point. The benchmark table was only at 6-9 MB, which
left the honest question unanswered: the FFI boundary is not free, so below
some size dart:convert wins.
bench/crossover.dartsweeps from 1 KB to 4 MB reading the same two fields through each engine, and the README now carries the curve. The crossover for the lazySimdJsonDocument.atpath is around 2 KB: below it dart:convert decodes faster than it takes to cross into native code (0.4x at 1 KB), above it simdjson pulls away (5.7x at 4 KB to 10.4x at 4 MB). This is the number to decide adoption on, and it says plainly: use it for reading part of a payload that is more than a few KB, not for small ones.
0.2.3 #
- Widen the native-toolchain constraints so the package can be installed in a
Flutter app at all.
hooks2.1.0 andnative_toolchain_c0.19.3 raised theirmetafloor to ^1.19.0, and Flutter's SDK pinsmetato 1.17.0, soflutter pub addfailed at version solving with "flutter from sdk is incompatible". Allowinghooks >=2.0.2andnative_toolchain_c >=0.19.2lets the solver pick a version that works with the pinnedmeta, while a pure-Dart project still resolves to the newest. No API or behaviour change.
0.2.2 #
- Shorten the screenshot description. pub.dev accepts up to 200 characters but scores only those under 160, so the previous release published cleanly and quietly gave up the documentation points it was meant to earn.
0.2.1 #
- Declare the benchmark chart in
pubspec.yamlso pub.dev renders it on the package page. The chart was already in the repository and the README, but pub.dev shows only what thescreenshots:field points at, so the page a reader lands on from search opened with text where the measurement should have been.
0.2.0 #
- Add
simdJsonDecodeNdjsonandsimdJsonDecodeNdjsonBytesfor newline-delimited JSON (.ndjson,.jsonl, log streams). The whole buffer goes to simdjson's document stream in one native pass and comes back as one decoded value per document, instead of ajsonDecodecall per line. Measured at 10.4 ms against 17.8 ms on a 2.11 MB, 20,000-document log (Apple M-series, warmed up, five-run average), about 1.7x. - A truncated last document is reported as a
FormatExceptionrather than dropped. simdjson's document stream treats trailing bytes that do not form a complete document as something a later batch will finish, which for a whole-buffer parse would silently lose the last record of a cut-off log.
0.1.3 #
- Example: rewrite it around the real use case. It now builds a multi-megabyte paginated payload and pulls a few fields (the page header and first record) by selective access, which is where the package is faster than a full decode, alongside the full-decode path and a rejected-input case.
0.1.2 #
- Docs: tightened the README and added an architecture diagram.
0.1.1 #
- Rename the example file to match the package name so pub.dev picks it up.
0.1.0 #
Initial release, vendoring simdjson 4.6.4.
SimdJsonDocument: parse once, materialize only the subtrees you read, addressed by RFC 6901 JSON Pointers. 5-15x faster than full decoding when reading selected fields from large documents.simdJsonDecodeBytes/simdJsonDecode: whole-document decoding with the same result shapes asjsonDecode.- Native code builds automatically via Dart build hooks (Dart 3.10+); no manual native setup.