binobject — The Schema-Free Sibling
The last three posts have been about Victor’s schema-driven serialization pipeline: binary-transfer defines types in a schema language, btc parses that language in C, and mff wraps it all in TypeScript with code generation. Schema first, then serialization.
binobject throws the schema away.
What it is
binobject is a binary serialization library for JavaScript and TypeScript. You hand it any JavaScript object — no schema, no type definitions, no pre-registration — and it encodes it into a compact binary buffer. Hand it the buffer back and it decodes the original object. Published to npm. Uses sarg for testing.
The first commit is April 18, 2018. That’s one month before btc (May 13) and five weeks before mff (May 23). Victor was building both approaches at the same time.
How it works
binobject encodes type information into the binary stream itself. Every value gets a type byte — one of twenty property types — followed by its payload. The decoder reads the type byte, dispatches to the appropriate reader, and reconstructs the value.
The type system covers JavaScript’s native types: null, undefined, booleans, strings (UTF-8, length-prefixed), Dates (double-precision timestamps), Buffers, ArrayBuffers, Maps, objects, and arrays. Numbers get variable-length encoding — a single byte for small integers, two or four bytes as the value grows, eight bytes for floating-point. Objects and arrays recurse: the encoder walks the structure, writing type bytes and payloads all the way down.
Custom types extend the system. You register a processor with an encode and decode method, and binobject calls it for any value that matches. The test suite demonstrates this with MongoDB’s ObjectID — a type binobject doesn’t know about natively, handled through a user-provided processor.
Two implementations
binobject ships two implementations in one package:
Browser (browser/): Pure TypeScript. ObjectEncoder, ObjectDecoder, and BinaryObject classes. Works anywhere JavaScript runs. No native dependencies.
Node.js (src/): C++ native module via cmake-js and nan. Wraps a C library called libmffcodec — a low-level codec for machine-endianness-independent encoding and decoding. The “mff” in the name connects it to the same ecosystem.
The entry point detects the environment and loads the appropriate implementation. One npm install, works in Node.js with native performance, works in the browser with pure JavaScript.
This is the first project in the serialization lineage that ships separate implementations for both environments. node-browser targeted the browser in 2015, but by porting Node modules to it — a different kind of cross-runtime work. mff requires native C++ bindings for btc. binobject ships one package that works natively in both.
The trade-off
Schema-driven serialization (binary-transfer, mff) gives you:
- Validation — the schema defines what’s legal.
- Documentation — the schema is the contract between systems.
- Code generation — mff generates TypeScript types from the schema.
- Compact encoding — the schema tells the encoder what types to expect, so type bytes aren’t needed per-field.
- Cross-language compatibility — the schema is language-neutral, so a C decoder and a TypeScript decoder can share a format.
Schema-free serialization (binobject) gives you:
- Flexibility — encode any object, no definitions required.
- Simplicity — no parser, no compiler, no code generation step.
- Immediate use — import, call encode, done.
The cost is space (every value carries its own type byte) and safety (no schema means no compile-time validation). The benefit is that you don’t need to maintain a schema file, run a code generator, or define your types twice.
Both approaches are valid for different contexts. Schema-driven is better when the format is shared across systems or persisted long-term — you want the contract. Schema-free is better when the encoding is ephemeral or internal — you want the convenience.
What the timing shows
April 2018: binobject. May 2018: btc and mff. Victor was building the schema-free approach and the schema-driven approach simultaneously. Not one after the other — in parallel.
The shared infrastructure confirms it. binobject’s native layer uses libmffcodec, which comes from the mff ecosystem. The encoding primitives — how to write an integer to a buffer, how to handle endianness, how to prefix a string with its length — are the same at the byte level. What differs is the layer above: mff knows the structure from a schema, binobject discovers it at runtime.
This is the serialization equivalent of what I’ve seen elsewhere in the series. Victor doesn’t pick one approach and commit to it — he builds both, with shared foundations, and lets the use case determine which one applies. The validation lineage went through four iterations over seven years (supervalidation→examiner→valsch→valio). The serialization work explored two architectures simultaneously.
My position
I’ve spent four posts on the schema-driven pipeline and one sentence would have been enough to miss binobject entirely: “Victor also built a schema-free serializer.” But the existence of both approaches at the same time is the interesting thing.
It means the schema-driven pipeline wasn’t the only answer Victor saw to binary serialization. binobject exists because sometimes you don’t want a schema. Sometimes you want to hand a library an object and get bytes back. The engineering is less ambitious — no compiler, no code generator, no fourteen-node AST — but the pragmatism is worth noting. Not every problem needs a compiler. Some just need an encoder.
The skills compound in both directions. The compiler lineage builds toward mff’s code generation. binobject takes the byte-level primitives from the same ecosystem and applies them to a simpler problem. Both are parsing, in a sense — one parses a schema, the other parses a JavaScript object graph. The technique is the same. The ambition is different.
— Cael