shorn

Binary serialization for people who already validate.

Your Zod, Valibot, or ArkType schema already describes your data. It describes your bytes too:

// The schema you already wrote — not a copy of it.const Person = z.object({  name: z.string(),  age: z.int().nonnegative(),  sex: z.enum(["M", "F", "X"]),}); // It is also the wire format.const bytes = encode(Person, person);// → Uint8Array(8), validated on the way in const back = decode(Person, bytes);// → { name: string; age: number; sex: "M" | "F" | "X" }
One cell is one byte. shorn uses the validator you already have.
shorn 8
Avro (second schema) 8
SchemaPack (custom DSL) 9
Protobuf (.proto + codegen) 11
msgpackr (3.1× larger) 25
JSON (4.4× larger) 35
Read the docs npm install shorn

Try it

Your schema, your payload, the real encoder, in this tab.

loading the encoder…

No codegen, no schema file

Other schema codecs require a second definition, such as a .proto, an .avsc, or a builder call. You then need to generate code and keep both definitions in sync. shorn uses the schema you already validate with.

The tradeoff is no automatic schema evolution. Because the bytes have no field tags, fingerprinted() detects a changed schema but cannot resolve it. This works well for caches and RPC, but long-lived data needs versioned codecs.

Encode and decode speed

shorn beat JSON on every fixture: 1.2–3.4× faster to encode and 1.8–5.4× faster to decode. Only shorn validates in this comparison. Avro is still faster at encoding, and msgpackr shared records decode nested and batch data faster. Results are operations per second; higher is better.

Fixture shorn enc JSON enc shorn dec JSON dec
Person 14.1M 4.14M 23.7M 4.41M
Unicode person 6.83M 3.48M 6.03M 3.33M
Nested event 3.92M 1.32M 4.17M 1.66M
100-event batch 43.1k 35.0k 43.2k 19.9k
Person, validated 6.62M 3.49M 7.85M 3.44M

The ASCII payloads use 23–26% as many bytes as JSON; the Unicode payload uses 53%. shorn also wins nine of ten comparisons against JSON.stringify, even though that baseline stops at a string instead of producing bytes. Measured on Node 22.23 and an Apple M4 Pro; median of seven samples. Full results.