Champollion’s technical signature lies in two points: a hand-written JSON-P state-machine parser designed to minimize allocation, and a compile-time JSON-B codegen via APT that removes runtime reflection. This page details both pipelines.
Modules overview
champollion-jsonb depends on champollion-jsonp. Never the reverse — the binding layer composes on the processing layer.
JSON-P pipeline — pull parser
The parser implements an RFC 8259 state machine scanning character by character, as branchless as possible.
stateDiagram-v2
[*] --> START
START --> IN_OBJECT: '{'
START --> IN_ARRAY: '['
START --> VALUE: literal
IN_OBJECT --> KEY: '"'
KEY --> AFTER_KEY: '"'
AFTER_KEY --> VALUE: ':'
VALUE --> AFTER_VALUE
AFTER_VALUE --> IN_OBJECT: ','
AFTER_VALUE --> END_OBJECT: '}'
AFTER_VALUE --> IN_ARRAY: ','
AFTER_VALUE --> END_ARRAY: ']'
END_OBJECT --> [*]
END_ARRAY --> [*]
Key components (package io.vidocq.champollion.internal.jsonp.parser):
-
ChampollionJsonParser— heart of the state machine, exposes theJsonParserAPI. -
Char-by-char tokenizer, branchless when possible (transition tables instead of
switch). -
BOM detection for UTF-8 / UTF-16 BE/LE / UTF-32 BE/LE per RFC 8259 + JSON-P §3.3.
-
Simple
BufferPool(bounded MPMC queue), no external dependency — discovered throughJson.createParserFactory(Map).
The symmetric JsonGenerator pushes through an automatically buffered Writer (BufferedWriter interposed on any non-already-buffered Writer). Precompiled RFC 8259 §7 escape table for control characters.
JSON-B pipeline — runtime introspection (default mode)
sequenceDiagram
participant App
participant Jsonb as ChampollionJsonb
participant Cache as ClassValue<BindingPlan>
participant MH as MethodHandles
participant Gen as JsonGenerator
App->>Jsonb: toJson(person)
Jsonb->>Cache: get(Person.class)
alt cache miss
Cache->>MH: privateLookupIn + unreflect
MH-->>Cache: BindingPlan{writers...}
end
Cache-->>Jsonb: BindingPlan
loop for each PropertyWriter
Jsonb->>Gen: write(name, MH.invoke(person))
end
Gen-->>App: "{...}"
-
BindingPlanis computed once per class, lock-free reads viaClassValue. -
PropertyWriter/PropertyReaderrely onMethodHandles— nosetAccessible(true)at runtime, the consumer mustopensits package. -
Customization (
@JsonbProperty,@JsonbDateFormat,@JsonbTypeAdapter, etc.) is baked into the plan once.
JSON-B pipeline — static codegen (APT)
This is Champollion’s identity-defining strength. Rather than introspecting at runtime, the champollion-codegen-apt annotation processor emits at compile time a <Type>$$Binding.java that implements JsonbBinding<T> directly, without reflection.
Key traits:
-
Generated Java code, no bytecode in
champollion-codegen-apt. Bytecode comes viajavacon the generated source — debuggable, readable. -
Class-File API (JEP 484) considered for secondary passes (precomputed UTF-8 byte-array constants in bytecode). No Byte Buddy, no ASM.
-
META-INF/services/io.vidocq.champollion.jsonb.spi.BindingFactoryProvider: ServiceLoader auto-discovery byChampollionJsonb. -
Runtime fallback: if no static binding found,
ChampollionJsonbfalls back to M4 introspection. Logged INFO; flagchampollion.jsonb.warn-on-fallback=trueto audit in production. -
AOT-friendly: zero reflection ⇒ GraalVM
native-imagewith no config, Leyden CDS happy. -
Optimizations in the generated binding: property names as precomputed UTF-8
byte[]constants, stable write order, grouped writes (writeStartObject+ firstwritemerged via thewriteStringfast-path).
champollion-codegen-maven-plugin is a Maven orchestrator that scans the host project’s classpath and invokes the APT on JSON-B types detected but not annotated @JsonbStatic (useful for third-party classes).
Virtual threads & ScopedValue
-
No
synchronized, no exportedThreadLocal. -
Context propagation during
toJson/fromJson(current config, recursion guard) goes throughScopedValue(JsonbContext.CURRENT). -
A parser pool is exposed JPMS-qualified:
exports io.vidocq.champollion.internal.jsonp.parser to io.vidocq.champollion.jsonb(cf. lien:https://codeberg.org/Vidocq/champollion/src/branch/main/BENCH.md[BENCH.md] §7.4).
AOT compatibility
GraalVM |
Static mode: zero config required (no reflection). Runtime mode: requires reachability metadata for bound classes. |
Leyden CDS |
Static mode: fully supported; AppCDS preloads |
Java Module System |
Strict |
Why this architecture?
-
Zero dependency: Champollion embeds in any runtime without dragging Parsson, Yasson, Jackson, or their transitives. Minimal attack surface, fast startup.
-
Codegen over reflection: predictable (no first-hit surprise), AOT-friendly, debuggable (the generated code is readable).
-
Strict JPMS: strong encapsulation,
internal.*may break between versions without notice. -
Virtual threads: modern REST servers (Cassini) run thousands of concurrent threads — no contention point in the serializer.