La singularité technique de Champollion tient en deux points : un parser JSON-P state-machine taillé à la main pour minimiser les allocations, et un codegen JSON-B compile-time via APT qui élimine la réflexion à chaud. Cette page détaille les deux pipelines.
Vue d’ensemble des modules
champollion-jsonb dépend de champollion-jsonp. Jamais l’inverse — la couche binding compose sur la couche processing.
Pipeline JSON-P — parser pull
Le parser implémente une state machine RFC 8259 scannant caractère par caractère, la plus branchless 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 --> [*]
Composants clés (package io.vidocq.champollion.internal.jsonp.parser) :
-
ChampollionJsonParser— cœur de la state machine, expose l’APIJsonParser. -
Tokenizer caractère par caractère, branchless dès que possible (table de transitions plutôt que
switch). -
Détection BOM UTF-8 / UTF-16 BE/LE / UTF-32 BE/LE conformément à RFC 8259 + JSON-P §3.3.
-
BufferPoolsimple (file MPMC bornée), pas de dépendance externe — découvert viaJson.createParserFactory(Map).
Le JsonGenerator symétrique pousse via un Writer automatiquement bufferisé (BufferedWriter interposé sur tout Writer non déjà bufferisé). Escape RFC 8259 §7 précompilé pour les caractères de contrôle.
Pipeline JSON-B — runtime introspectif (mode défaut)
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: "{...}"
-
BindingPlanest calculé une fois par classe, lecture lock-free viaClassValue. -
PropertyWriter/PropertyReaderadossés àMethodHandles— pas desetAccessible(true)à chaud, le consommateur doitopensson package. -
Customization (
@JsonbProperty,@JsonbDateFormat,@JsonbTypeAdapter, etc.) compilée dans le plan une seule fois.
Pipeline JSON-B — codegen statique (APT)
C’est le point fort identitaire de Champollion. Plutôt que d’introspecter à chaud, l’annotation processor champollion-codegen-apt génère à la compilation un <Type>$$Binding.java qui implémente JsonbBinding<T> directement, sans réflexion.
Caractéristiques :
-
Code Java généré, pas de bytecode dans
champollion-codegen-apt. Le bytecode arrive viajavacsur le source généré — débuggable, lisible. -
Class-File API (JEP 484) envisagée pour des passes secondaires (constantes pré-encodées en bytecode UTF-8). Pas de Byte Buddy, pas d’ASM.
-
META-INF/services/io.vidocq.champollion.jsonb.spi.BindingFactoryProvider: ServiceLoader auto-découverte parChampollionJsonb. -
Runtime fallback : si aucun binding statique trouvé,
ChampollionJsonbretombe sur l’introspection M4. Loggué INFO ; flagchampollion.jsonb.warn-on-fallback=truepour audit en prod. -
Compatible AOT : zéro réflexion ⇒ GraalVM
native-imagesans config, Leyden CDS heureux. -
Optimisations dans le binding généré : noms de propriétés en
byte[]constants pré-encodés UTF-8, ordre des écritures stable, écritures groupées (writeStartObject+ premierwritefusionnés viawriteStringfast-path).
champollion-codegen-maven-plugin est un orchestrateur Maven qui scanne le classpath du projet hôte et invoque l’APT sur les types JSON-B détectés mais non annotés @JsonbStatic (utile pour les classes tierces).
Virtual threads & ScopedValue
-
Aucun
synchronized, aucunThreadLocalexporté. -
La propagation contextuelle pendant
toJson/fromJson(config courante, recursion guard) passe parScopedValue(JsonbContext.CURRENT). -
Un pool de parsers est exposé qualifié JPMS :
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).
Compatibilité AOT
GraalVM |
Mode statique : zéro config nécessaire (pas de réflexion). Mode runtime : nécessite reachability metadata pour les classes bindées. |
Leyden CDS |
Mode statique : pleinement supporté ; l’AppCDS pré-charge les |
Java Module System |
|
Pourquoi ce choix d’architecture ?
-
Zéro dépendance : Champollion s’embarque dans n’importe quel runtime sans entraîner Parsson, Yasson, Jackson, ou leurs transitives. Surface d’attaque minimale, démarrage rapide.
-
Codegen plutôt que réflexion : prévisible (pas de surprise au premier hit), AOT-friendly, débuggable (le code généré est lisible).
-
JPMS strict : encapsulation forte, on peut faire confiance à
internal.*pour casser entre versions sans préavis. -
Virtual threads : les serveurs REST (Cassini) modernes tournent à des milliers de threads concurrents — pas de point de contention dans le sérialiseur.