Five formats, four axes: is there a schema, is it binary, does the schema ride with the data, and can you read a field without parsing the whole thing? Answer those and the right pick falls out.
| Format | Schema | Binary | Self-describing | Zero-copy | Sweet spot |
|---|---|---|---|---|---|
| JSON | None | β Text | β Names inline | β | Public APIs, config, logs |
| Protobuf | Required (.proto) | β | Tags only | β | Internal RPC, gRPC |
| Avro | Required (.avsc) | β | Schema stored with data | β | Data lakes, Kafka, batch |
| MessagePack | None | β | β Names inline | β | "binary JSON" drop-in |
| FlatBuffers | Required (.fbs) | β | Tags only | β Read in place | Games, mmap, latency-critical |
Both are schema-ful and binary, but they differ in where the schema lives:
MessagePack is JSON's data model (maps, arrays, strings, numbers, bools, null) packed into bytes. No schema, still self-describing β field names travel with the data β but numbers and structure are binary, so it's smaller and faster to parse than text JSON.
Protobuf, Avro, and MessagePack all deserialize: they walk the bytes and build an object graph
before you can touch a field. FlatBuffers lays out data so that a field's location is computable from
offset tables β you read obj.hp() directly out of the received buffer with
no parse step and no allocation.
| If you... | Use |
|---|---|
| expose a public / browser-facing API | JSON |
| run internal RPC between your own services | Protobuf (via gRPC) |
| stream/store billions of records with evolving schemas | Avro |
| want smaller JSON without adopting a schema | MessagePack |
| need to read a few fields from big buffers with zero parse cost | FlatBuffers |