Skip to content

Jackson 3 port - #95

Open
vincentjames501 wants to merge 3 commits into
metosin:masterfrom
vincentjames501:jackson-3-port
Open

Jackson 3 port#95
vincentjames501 wants to merge 3 commits into
metosin:masterfrom
vincentjames501:jackson-3-port

Conversation

@vincentjames501

@vincentjames501 vincentjames501 commented Aug 1, 2026

Copy link
Copy Markdown

See #89

This isn't intended to be merged as-is but rather start a discussion.

  1. Should we even migrate to jackson 3?
  2. Should we consider some of the optimizations implemented?
  3. How should versioning be done?
  4. Are we OK with the breaking changes or should we try to preserve more compatibility?

#89 (comment)

In the spirit of full transparency, this was mostly done by Claude Fable (probably obvious from some the word slop). If we are interested in going forward, I'm happy to do more human iteration on this and clean things up a bit.

Three decoder optimizations, measured together as +15-23% decode
throughput with string keys and +28-48% with keyword keys vs 1.0.0
at 100B+ payloads (bench/2026-08-01-full-comparison.md):

- KeywordKeyDeserializer fronts Keyword.intern (fresh Symbol + global
  weak-ref table per call) with a bounded String -> Keyword cache using
  the same clear-when-full strategy as jackson-core's own
  tools.jackson.core.util.InternCache.

- JSON objects of 8 entries or fewer decode to PersistentArrayMaps,
  matching small Clojure map literals (and cheshire), instead of paying
  hashing + HAMT node churn in a transient PersistentHashMap; larger
  objects spill to the transient as before, and duplicate keys keep
  assoc semantics via createAsIfByAssoc. Property loops use nextName(),
  jackson-core's fused nextToken()+currentName() fast path.

- ClojureUntypedDeserializer makes untyped (Object-targeted) decoding a
  single recursive switch instead of double-dispatching through
  databind's UntypedObjectDeserializer. Non-stock Map/List deserializers
  (java-collection-module, jsonista.tagged, user modules) are still
  delegated to; the wiring lives in resolve() because runtime
  findNonContextualValueDeserializer lookups skip contextualization.

Also tried and rejected on measurement: SerializedString caching in
KeywordSerializer (2-4% regression on the write-value-as-string path;
bench/2026-07-31-kw-optimizations.md). Its escaping-behavior tests are
kept.
Benchmark harness:
- :encode-kw/:decode-kw/:encode-kw-bytes groups measure the
  keyword-keys-object-mapper workload against data.json (:key-fn) and
  cheshire (parse-string true)
- encode-jsonista/decode-jsonista no longer shadowed by tagged-module
  variants, so :encode/:decode measure stock jsonista

Results (bench/2026-08-01-full-jackson321.txt on Jackson 3.2.1, method
+ controls in bench/2026-08-01-full-comparison.md): vs jsonista
1.0.0/Jackson 2, decode is +15-23% with string keys and +28-48% with
keyword keys at 100B+; encode is unchanged at 1KB+, and the sub-100B
encode regression is in the raw Jackson 3 engine, not jsonista. README
throughput tables and graphs regenerated from this run; CHANGELOG
carries the summary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 📬 Inbox

Development

Successfully merging this pull request may close these issues.

2 participants