Protobuf vs JSON

The same order is 42 bytes as protobuf and 137 bytes as compact JSON. Protobuf is smaller because it sends field numbers instead of names and writes numbers as varints instead of text; JSON can be read without a schema. Protobuf suits calls between services that share a .proto; JSON suits payloads that people, browsers or other companies read.

Updated

At a glance

ProtobufJSON
On the wireBinary: a field number and wire type, then the valueText: each field's name, then its value
SchemaRequired: a .proto file, compiled in or loaded at run timeOptional, such as JSON Schema or OpenAPI
Reading a payloadNeeds the schema; without it a raw decode shows only field numbersAny text editor
Renaming a fieldSafe: the name is not on the wire, only the numberBreaks every reader of the old name
Adding a fieldOld readers skip a field number they do not knowMost readers ignore a key they do not know
64-bit integersExactJavaScript loses precision past 2^53, so ProtoJSON writes them as strings
Binary dataRaw bytesBase64 text, about a third larger

Worked example: one order, both ways

The message:

syntax = "proto3";
package shop.v1;

message Order {
  string id = 1;
  int64 customer_id = 2;
  repeated LineItem items = 3;
  int32 total_cents = 4;
  bool paid = 5;
}

message LineItem {
  string sku = 1;
  uint32 quantity = 2;
}

As JSON, 137 bytes when written without spaces:

{
  "id": "ord_1842",
  "customerId": "90210",
  "items": [
    {
      "sku": "MUG-01",
      "quantity": 2
    },
    {
      "sku": "TEE-L",
      "quantity": 1
    }
  ],
  "totalCents": 4800,
  "paid": true
}

As protobuf, 42 bytes, 31% of the JSON:

FieldBytes (hex)Size
id0a 08 6f 72 64 5f 31 38 34 3210
customer_id10 e2 c0 054
items (first)1a 0a 0a 06 4d 55 47 2d 30 31 10 0212
items (second)1a 09 0a 05 54 45 45 2d 4c 10 0111
total_cents20 c0 253
paid28 012

71 of the JSON's 137 bytes are field names and their quotes; protobuf spends one byte per field on a key that holds the field number and wire type. Numbers shrink too: 90210 takes 3 bytes as a varint, and true takes 1.

Paste CghvcmRfMTg0MhDiwAUaCgoGTVVHLTAxEAIaCQoFVEVFLUwQASDAJSgB into the protobuf decoder to read these bytes back without the .proto.

Converting protobuf to JSON

The official protobuf libraries include the JSON mapping, called ProtoJSON: protojson.Marshal in Go, JsonFormat.printer() in Java and json_format.MessageToJson in Python. It writes names in lowerCamelCase, 64-bit integers as strings, enums by name and bytes as base64, and it leaves out fields at their default value unless you ask for them.

Timestamps, durations and the other well-known types have JSON forms of their own; see well-known types in JSON. To get the JSON request for any message in a .proto, use the .proto to JSON tool.

Speed

This page measures size only. How fast each one encodes and parses depends on the language, the library and the message, so measure with your own messages before choosing on speed.

When JSON is the better choice

  • A public API that other companies or browsers call without your .proto.
  • Configuration, logs and payloads people read and edit by hand.
  • Services that do not share a schema, or change shape faster than one can be agreed.

When protobuf is the better choice

  • Calls between services you own, where every side can load the same .proto.
  • Large or frequent messages, where the bytes on the wire add up.
  • Schemas that evolve over years: field numbers let old and new versions read each other.

istek sends the JSON you write as protobuf and shows every reply as JSON, decoded with its schema. See istek.

istek, $29 once

macOS 14.0+ · Apple Silicon · no account, no telemetry

Buy istek · $29