vs Avro, Protobuf, SchemaPack
These three share shorn’s core idea — the schema is known out of band, so the payload need not describe itself.
| Codec | Person | Nested event | 100 events |
|---|---|---|---|
| shorn | 8 | 43 | 4,135 |
| Avro / avsc | 8 | 44 | 4,249 |
| SchemaPack | 9 | 44 | 4,235 |
| Protobuf.js | 11 | 57 | 5,684 |
shorn wins or ties on every fixture, but the difference from Avro and SchemaPack is only 0–3%. Size alone would not justify a new library. Protobuf is 30% larger on the nested event because it includes field tags. Those tags add overhead but enable schema evolution.
shorn now leads both directions on every fixture but Unicode. Avro was the last codec ahead of it on encode; generated record encoders and the framing work around them closed that.
| Fixture | Op | shorn | Avro | SchemaPack |
|---|---|---|---|---|
| Person | enc | 25.16M | 17.10M | 12.55M |
| Person | dec | 67.55M | 25.59M | 16.12M |
| Nested event | enc | 8.64M | 6.42M | 4.19M |
| Nested event | dec | 11.45M | 5.44M | 4.94M |
| 100 events | enc | 100.1K | 41.2K | 53.4K |
| 100 events | dec | 116.3K | 52.8K | 57.5K |
Take the Person encode margin conservatively. Every codec in that table shares one process, and Avro’s Person encode read 21.06M, 20.72M and 21.69M before this change against 17.10M after it — with 20.74M when measured alone in its own process. Isolated single-codec runs on the same machine put shorn at 42.54 ns and Avro at 48.22 ns, a 13% lead rather than the 47% above. The other rows’ margins are wide enough that this does not reorder them. See Throughput.
With validation on both ends shorn leads both directions, though validation cost dominates each:
| Codec | Bytes | Encode | Decode |
|---|---|---|---|
| shorn + Zod | 8 | 8.93M | 12.07M |
| Zod + Avro | 8 | 8.47M | 10.10M |
| Zod + SchemaPack | 9 | 7.00M | 7.90M |
There is no longer a measured encode gap to Avro. The comparison below is kept only because it still bounds what a hand-written codec can do; it predates the allocation improvements, the generated encoders and the framing work, and has not been rerun. Raw Person encoding now takes about 40 ns instead of 204.7 ns. The hand-written codec keeps every check performed by the interpreter and produces identical bytes:
| Interpreted | Fair codegen | Win | |
|---|---|---|---|
| Person encode | 204.7 ns | 54.3 ns | 150.4 ns (73%) |
Both directions now compile: an object schema with no optional fields builds its own record decoder and record encoder with new Function. That plus three fixes to the framing around the schema walk — the pooled-writer hand-off, finish(), and a one-pass Writer.string — is what took every encode fixture but Unicode past Avro. What is left on the write path is the Writer, whose per-leaf method call the generated source does not yet inline. Both paths fall back to the interpreter under a Content Security Policy that forbids new Function. See Throughput.
What each asks of you
Section titled “What each asks of you”| Codec | What it requires |
|---|---|
| shorn | You already use a Standard Schema validator |
| Avro | A second schema model, kept in sync; Avro concepts leak into the API |
| Protobuf | A .proto file, a compiler or reflection step, generated code, per-field tags. Also 187.75 µs cold and 25.93 KB gzip |
| SchemaPack | A custom DSL, weak TS inference, a Node Buffer heritage needing a browser polyfill |
shorn’s main difference is not size or speed. It is that you do not maintain a second schema.
Evolution and cross-language: they win
Section titled “Evolution and cross-language: they win”| Codec | Evolution |
|---|---|
| Avro | Full reader/writer resolution. Mature, cross-language |
| Protobuf | Field tags give append-only compatibility. Mature, cross-language |
| SchemaPack | None |
| shorn | Mismatch detection only |
Use Avro when you need automatic schema resolution. It ties shorn on size by keeping the writer’s schema out of band. Reproducing Avro’s evolution model is outside shorn’s scope. See Schema Evolution.
Avro and Protobuf have mature implementations in every language you need. shorn is TypeScript only — which is what lets it use JSON Schema instead of defining an IDL.
Footprint
Section titled “Footprint”| Codec | Minified | Gzip |
|---|---|---|
| shorn | 17.57 KB | 5.39 KB |
| protobufjs/light | 88.35 KB | 25.93 KB |
avsc needs a browser stream polyfill and SchemaPack a buffer polyfill, so neither has a clean browser number. Cold setup: shorn 52–66 µs, Avro 68.99 µs, Protobuf.js 187.75 µs, SchemaPack 3.00 µs.
Choosing
Section titled “Choosing”| Use | When |
|---|---|
| shorn | TypeScript both ends, already validating, want no second schema and the smallest payload |
| Avro | Cross-language, or real schema evolution |
| Protobuf | Cross-language with an existing gRPC ecosystem |
| SchemaPack | Node-only, raw speed, a custom DSL is acceptable |
Generated formats — Bebop, FlatBuffers, Cap’n Proto — trade an IDL and compiler step for cross-language support and generated-code speed. Qualitative competitors until shorn has a specialized backend.