Protobuf to JSON
Decode Protobuf bytes to JSON with or without a .proto file. Hex, Base64 or gRPC frames in; ProtoJSON out with int64 and Timestamp intact. Encode JSON back too.
- Runs in your browser
- Your data never leaves your browser
- Free · No Sign-Up
Scan with WeChat to share this tool
Examples, details and FAQ Worked examples, how it compares with other tools, and answers to common questions.
Example: an Order Message
The example loaded on this page is an Order from a small shop schema with an int64 id above 2^53, a google.protobuf.Timestamp and a map<string, string>. Decoded as shop.v1.Order, the 76 bytes give:
{
"orderId": "9007199254740993",
"customerName": "Ada Lovelace",
"items": [
{
"sku": "BK-1843",
"quantity": 2,
"unitPrice": 19.99
}
],
"status": "STATUS_PAID",
"createdAt": "2026-10-01T08:30:00.250Z",
"labels": {
"channel": "web"
}
}
Each line follows the ProtoJSON format: order_id becomes orderId, the 64-bit id is a string, the enum is its name, the Timestamp is an RFC 3339 string in UTC with 3 fraction digits, and the map is a JSON object. Number(9007199254740993) in JavaScript is 9007199254740992, because Number.MAX_SAFE_INTEGER is 2^53 − 1; that is why the format uses strings.
Example: No .proto at Hand
With No schema (raw decode) the same bytes print with field numbers. The protoc —decode_raw view gives exactly what protoc --decode_raw prints:
1: 9007199254740993
2: "Ada Lovelace"
3 {
1: "BK-1843"
2: 2
3: 0x4033fd70a3d70a3d
}
4: 1
5 {
1: 1790843400
2: 250000000
}
6 {
1: "channel"
2: "web"
}
0x4033fd70a3d70a3d is the 64-bit pattern of the double 19.99, and 5 { 1: 1790843400 2: 250000000 } is the Timestamp’s seconds and nanos. JSON with other readings shows each number as its other possible types (sint64, sfixed64, double, float), so you can tell a signed field from an unsigned one. Note that protoc prefers a nested message over a string: the two bytes hi read as field 13 with value 105. The JSON views show readable text as a string instead.
Example: A gRPC-Web Response
A application/grpc-web-text response is Base64 of the gRPC frames, the last one carrying the trailers. Copied from the browser’s network panel, this body:
AAAAAEwIgYCAgICAgBASDEFkYSBMb3ZlbGFjZRoUCgdCSy0xODQzEAIZPQrXo3D9M0AgASoLCIi0+NUGEIDlmncyDgoHY2hhbm5lbBIDd2VigAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=
decodes to the same order. The tool removes the 5-byte header (flag and big-endian length, as in gRPC over HTTP/2), and lists grpc-status:0 from the trailers frame, whose flag has its top bit set (gRPC-Web), in the notes:
{
"order_id": "9007199254740993",
"customer_name": "Ada Lovelace",
"items": [
{
"sku": "BK-1843",
"quantity": 2,
"unit_price": 19.99
}
],
"status": "STATUS_PAID",
"created_at": "2026-10-01T08:30:00.250Z",
"labels": {
"channel": "web"
}
}
This run used Field names: As in .proto. A streaming response with several messages prints as a JSON array.
Example: JSON Back to Bytes
In JSON → Binary, this ProtoJSON (note the +09:00 offset, which ProtoJSON allows on input):
{"orderId": "42", "customerName": "Ada", "status": "STATUS_SHIPPED", "createdAt": "2026-10-01T17:30:00+09:00"}
encodes to 17 bytes, the same as protoc --encode=shop.v1.Order for that message:
08 2a 12 03 41 64 61 20 02 2a 06 08 88 b4 f8 d5 06
Both orderId and order_id are accepted, and so are enum numbers and int64 as JSON numbers. Mistakes are reported with the line and column: "status": "PAID" is rejected as not a value of shop.v1.Status, followed by the valid names, and a misspelled key such as customer_Name gets a Did you mean “customerName”? hint. Add gRPC frame prefixes the 5-byte header, and the output can be hex, Base64, a Python bytes literal or a C array, or downloaded as a .bin file.
What the Output Follows
The decoder and encoder were checked on 140 random messages against protoc 36.2 and Python protobuf 7.36.2 json_format, including proto2 groups and extensions and edition 2023 features (139 match; the one Python rejects, the tool rejects too):
| Data | JSON |
|---|---|
| int64, uint64, sint64, fixed64, sfixed64 | decimal string |
| float, double | number; "NaN", "Infinity", "-Infinity"; floats print the shortest form (0.1, not 0.10000000149011612) |
| bytes | standard Base64 |
| enum | name; a number the schema does not define stays a number |
| Timestamp, Duration, FieldMask | "2026-10-01T08:30:00Z", "1.5s", "user.displayName" |
| Struct, Value, ListValue, wrappers | plain JSON values |
| Any | "@type" plus the fields, or "value" for a well-known type |
| default values | left out for proto3 fields without presence |
How It Differs From Other Online Decoders
Tested on 2026-10-02 with the order above. egohero.com and codertools.net use a .proto: with the Timestamp import, egohero stops with no such Type or Enum ‘google.protobuf.Timestamp’ and codertools lists no message types; with the Timestamp field removed, both print "order_id": "9007199254740992" (one less than the real id) and snake_case names. jsontotable.org in Base64 Binary mode printed "id": 123 and a name that is not in the input; only "originalLength": 76 was right. pawitp’s Protobuf Decoder reads the bytes without a schema, strips the gRPC header and shows sint and double readings, but takes no .proto. None of them sent the input to a server.
Limits
- Schemas are
.protosource. A compiled descriptor set (protoc --descriptor_set_out) or text-format messages cannot be loaded. Bundled imports areany,duration,empty,field_mask,struct,timestampandwrappers; other imports, such asgoogle/api/annotations.proto, must be added if a type comes from them. Custom options that extenddescriptor.protoare ignored, since options do not change the bytes. - Unknown fields are not in the JSON. ProtoJSON has no place for them; the notes list each with its field number, wire type and byte offset, and suggest message types that decode the bytes without any.
- Wrong type, valid output. Bytes carry no type name, so decoding with the wrong message often succeeds with odd values. Check the notes for unknown fields.
- Nesting depth is 100 and opened files are limited to 2 MB. Compressed gRPC frames are gunzipped; other
grpc-encodingvalues are not supported. - proto2 and editions (2023, 2024) are supported for groups, required fields, closed enums, extensions (
"[pkg.ext]"), field presence, packed and expanded repeated fields and delimited messages. A proto3stringthat is not valid UTF-8 is an error with its byte offset; usebytesfor binary data.
Related: inspect captured traffic in the HAR Analyzer & Viewer, convert payloads with Base64 Encode / Decode, and validate JSON with the JSON Schema Validator.
FAQ
Do I need the .proto file to decode Protobuf?
No. Leave the schema box empty and the tool decodes the wire format like protoc --decode_raw: you get field numbers, numbers, strings and nested messages. Field names, enum names, signed and 64-bit types and well-known types such as Timestamp need the .proto, because the bytes do not carry them.
Why are int64 values shown as strings?
The ProtoJSON format writes int64, uint64, sint64, fixed64 and sfixed64 as decimal strings, because JavaScript reads JSON numbers as doubles and loses digits above 2^53. The tool decodes 64-bit fields with BigInt, so 9007199254740993 stays 9007199254740993.
Why is a field missing from the JSON output?
ProtoJSON leaves out proto3 fields that hold their default value (0, empty string, false, first enum value) unless they are optional or in a oneof. Turn on Show fields with default values to print them. Fields whose numbers are not in the schema cannot appear in ProtoJSON at all; the Notes list each one with its byte offset.
How do I decode a gRPC or gRPC-Web payload?
Paste it as captured. With gRPC frame set to Auto, the tool removes the 5-byte header of each message, decodes every message in the body, shows gRPC-Web trailers in the notes and gunzips frames whose compressed flag is set. grpc-web-text bodies are Base64 and can be pasted directly.
Is my data uploaded?
No. Parsing, decoding and encoding run in your browser. The page stores only your option choices, not the schema, the bytes or the JSON.