By default, lutaml-model serializes through the same engines every Ruby install already has: Nokogiri (via moxml) for XML, Psych for YAML, the json gem for JSON.
The leptris family of native engines is a drop-in performance upgrade:
-
leptris — the XML engine moxml drives. When the
leptrisgem is in your bundle, moxml prefers it over Nokogiri for parsing and serialization. -
yeptris — the YAML/JSON engine. When the
yeptrisgem is in your bundle, lutaml-model’s YAML and JSON adapters resolve to the yeptris engine. -
teptris — the TOML engine. When the
teptrisgem is in your bundle it becomes the TOML default on every platform (0.2.12+ parses ~3x and dumps ~8x faster than tomlib, and the Windows mingw-ucrt prebuilts are back), giving Windows native TOML and retiring the tomlib-segfault workaround (previously pure-Ruby toml-rb only).
Opting in is purely a bundle decision — no code changes, no gemspec requirements:
bundle add leptris yeptris teptrisleptris and yeptris ship prebuilt platform gems for Linux and macOS (yeptris has no Windows prebuilts). teptris ships prebuilt gems for every platform, including Windows. When a gem is absent, lutaml-model automatically falls back to the standard engine for that format.
YJIT doubles the model pipeline
The model layer’s own work — mapping dispatch, casts, hydration — is plain Ruby and exactly the code shape YJIT compiles well; the native engines' C work is unaffected either way. On a controlled runner (ubuntu-latest, Ruby 3.4, the repository’s performance probe):
| op | YJIT off | YJIT on | change |
|---|---|---|---|
from_yaml | 75.1 i/s | 129.7 i/s | +73% |
to_yaml | 67.3 i/s | 145.1 i/s | +116% |
from_json | 73.2 i/s | 128.4 i/s | +75% |
to_json | 115.4 i/s | 255.5 i/s | +121% |
Enable it like any Ruby application does — this is the application’s call, never a gem’s:
RUBY_YJIT_ENABLE=1 ruby app.rb
# or in code, before heavy work:
RubyVM::YJIT.enableThe XML plan fast path moves work out of Ruby and into the engine, which shrinks the YJIT win — enabling both is still net faster than either alone.
The XML plan fast path (default on)
When the XML adapter resolves to leptris, XML (de)serialization compiles the model’s mapping into a leptris descriptor plan and runs the whole document through one native pass — measured 6.7x parse and 4x serialize with -76% / -80% allocations on the plan-path corpus. Models the plan cannot express exactly (same-name discriminator partitions, namespace-qualified models with deferred-rule shapes) and shapes with custom semantics (custom methods, polymorphism, unions, ordered children) hydrate through the interpretive pipeline or a per-subtree fragment of it, unchanged.
The path is on by default and is a no-op without leptris (standard adapters keep the classic pipeline). Opt out for the whole process:
Lutaml::Model::Config.configure do |config|
config.xml_plan_fast_path = false
endSelecting adapters explicitly
The engines register as ordinary adapter types, so explicit selection and the availability error messages work as usual:
Lutaml::Model::Config.xml_adapter_type = :leptris
Lutaml::Model::Config.yaml_adapter_type = :yeptris
Lutaml::Model::Config.json_adapter_type = :yeptris
Lutaml::Model::Config.toml_adapter_type = :teptrisSemantics notes
-
YAML: the yeptris adapter uses the engine’s
compat_11schema, so implicit typing matches Psych (parity-verified across the scalar battery: dates, timestamps, octal, sexagesimal,yes/no,.inf). Two deliberate differences: anchors and aliases always resolve, and a tagged value without a core type materializes as plain data instead of raisingPsych::DisallowedClass. -
JSON:
yeptrisparsing targets exactJSON.parsesemantics; generation stays on thejsongem. -
Error classes: parse failures keep the standard adapters' contract on every engine — the yeptris adapters re-raise the engine’s syntax errors as
Psych::SyntaxError(YAML, with the engine’s line, column, and problem text) andJSON::ParserError(JSON), and the model layer wraps them asInvalidFormatErrorjust as it does for Psych and thejsongem. Code that rescues those classes does not change when a native engine is added to the bundle. -
TOML: the teptris adapter mirrors tomlib value semantics — offset and local datetimes become
Time, dates becomeDate, local times stayString. Parse failures raiseTeptris::ParseErrorwith line and column, wrapped asInvalidFormatErrorlike the other TOML adapters.