The binary I/O system provides low-level reading and writing of Protocol Buffer wire format data. It consists of BinaryReader for parsing binary data and BinaryWriter for generating binary data, along with supporting encoder/decoder classes.
The BinaryReader class reads Protocol Buffer binary data with type checking and field navigation:
const { BinaryReader } = require('google-protobuf');
// Create reader from binary data
const reader = new BinaryReader(bytes, start, length, options);
// Alternative: Use cached allocation
const reader = BinaryReader.alloc(bytes, start, length, options);
// Navigate through fields
while (reader.nextField()) {
const fieldNumber = reader.getFieldNumber();
const wireType = reader.getWireType();
// Read field based on type
switch (fieldNumber) {
case 1:
const stringValue = reader.readString();
break;
case 2:
const int32Value = reader.readInt32();
break;
}
}
// Clean up (returns to cache if allocated)
reader.free();Type Definitions:
/**
* @constructor
* @param {!Uint8Array} bytes Binary data to read
* @param {number=} start Starting offset (default: 0)
* @param {number=} length Length to read (default: bytes.length)
* @param {BinaryReaderOptions=} options Reader options
*/
/**
* @typedef {Object} BinaryReaderOptions
* @property {boolean=} discardUnknownFields Whether to discard unknown fields
* @property {boolean=} aliasBytesFields Whether bytes fields should alias original buffer
*/const { BinaryReader } = require('google-protobuf');
// Field navigation
const hasNextField = reader.nextField();
const hasExpectedTag = reader.nextFieldIfTagEqualTo(expectedTag);
// Position information
const cursor = reader.getCursor();
const fieldCursor = reader.getFieldCursor(); // Position before current field
reader.advance(byteCount);
// Field information
const tag = reader.getTag();
const fieldNumber = reader.getFieldNumber();
const wireType = reader.getWireType();
const isEndOfGroup = reader.isEndGroup();
const isDelimited = reader.isDelimited();
// Buffer access
const buffer = reader.getBuffer();
const byteString = reader.getBufferAsByteString();
const isImmutable = reader.dataIsImmutable();
// Example: Manual field parsing
const reader = new BinaryReader(data);
while (reader.nextField()) {
console.log(`Field ${reader.getFieldNumber()}, Wire Type: ${reader.getWireType()}`);
if (reader.getWireType() === 2) { // DELIMITED
console.log(`Delimited field at cursor: ${reader.getCursor()}`);
}
}// Integer types
const uint32 = reader.readUint32();
const int32 = reader.readInt32();
const sint32 = reader.readSint32();
const uint64 = reader.readUint64();
const int64 = reader.readInt64();
const sint64 = reader.readSint64();
// Boolean
const boolValue = reader.readBool();
// Enum (reads as varint)
const enumValue = reader.readEnum();
// 64-bit values with split high/low parts (for precision)
const uint64Split = reader.readSplitUint64();
const int64Split = reader.readSplitInt64();
const sint64Split = reader.readSplitSint64();
// Example: Reading various integer types
while (reader.nextField()) {
switch (reader.getFieldNumber()) {
case 1:
const id = reader.readUint32();
break;
case 2:
const count = reader.readInt32();
break;
case 3:
const flag = reader.readBool();
break;
}
}// 32-bit fixed
const fixed32 = reader.readFixed32();
const sfixed32 = reader.readSfixed32();
const float = reader.readFloat();
// 64-bit fixed
const fixed64 = reader.readFixed64();
const sfixed64 = reader.readSfixed64();
const double = reader.readDouble();
// Split 64-bit reads (for precision)
const fixed64Split = reader.readSplitFixed64();
const sfixed64Split = reader.readSplitSfixed64();
// Example: Reading fixed-width fields
while (reader.nextField()) {
switch (reader.getFieldNumber()) {
case 1:
const timestamp = reader.readFixed64();
break;
case 2:
const temperature = reader.readFloat();
break;
case 3:
const coordinate = reader.readDouble();
break;
}
}// String and bytes
const stringValue = reader.readString();
const bytesValue = reader.readBytes();
// Messages (nested)
const messageBytes = reader.readMessage(subMessage, deserializeFn);
// Packed repeated fields
const packedInt32s = reader.readPackedInt32();
const packedUint32s = reader.readPackedUint32();
const packedBools = reader.readPackedBool();
const packedFloats = reader.readPackedFloat();
const packedDoubles = reader.readPackedDouble();
// ... other packed types
// Example: Reading delimited fields
while (reader.nextField()) {
switch (reader.getFieldNumber()) {
case 1:
const name = reader.readString();
break;
case 2:
const data = reader.readBytes();
break;
case 3:
const nestedMessage = new NestedMessage();
reader.readMessage(nestedMessage, NestedMessage.deserializeBinaryFromReader);
break;
case 4:
const numbers = reader.readPackedInt32();
break;
}
}const { BinaryReader } = require('google-protobuf');
// Skip fields by wire type
reader.skipVarintField(); // Skip varint field
reader.skipDelimitedField(); // Skip delimited field
reader.skipFixed32Field(); // Skip 32-bit fixed field
reader.skipFixed64Field(); // Skip 64-bit fixed field
reader.skipGroup(); // Skip group field (deprecated)
// Example: Selective field parsing with skipping
while (reader.nextField()) {
const fieldNumber = reader.getFieldNumber();
switch (fieldNumber) {
case 1:
const importantValue = reader.readString();
break;
case 2:
// Skip this field - not needed
reader.skipDelimitedField();
break;
case 3:
const anotherValue = reader.readInt32();
break;
default:
// Skip unknown fields
switch (reader.getWireType()) {
case 0: reader.skipVarintField(); break;
case 1: reader.skipFixed64Field(); break;
case 2: reader.skipDelimitedField(); break;
case 5: reader.skipFixed32Field(); break;
}
}
}The BinaryWriter class generates Protocol Buffer binary data:
const { BinaryWriter } = require('google-protobuf');
// Create writer
const writer = new BinaryWriter();
// Write fields
writer.writeString(1, "Hello World");
writer.writeInt32(2, 42);
writer.writeBool(3, true);
// Get result
const resultBuffer = writer.getResultBuffer(); // Uint8Array
const resultBytes = writer.getResultBufferAsByteString(); // ByteString
const resultBase64 = writer.getResultBase64String(); // Base64 string
// Reset for reuse
writer.reset();Type Definitions:
/**
* @constructor
* Creates a new binary writer for Protocol Buffer wire format
*/const { BinaryWriter } = require('google-protobuf');
const writer = new BinaryWriter();
// Integer types
writer.writeUint32(fieldNumber, uint32Value);
writer.writeInt32(fieldNumber, int32Value);
writer.writeSint32(fieldNumber, sint32Value);
writer.writeUint64(fieldNumber, uint64Value);
writer.writeInt64(fieldNumber, int64Value);
writer.writeSint64(fieldNumber, sint64Value);
// Boolean and enum
writer.writeBool(fieldNumber, boolValue);
writer.writeEnum(fieldNumber, enumValue);
// Split 64-bit writes (for precision)
writer.writeSplitUint64(fieldNumber, lowBits, highBits);
writer.writeSplitInt64(fieldNumber, lowBits, highBits);
writer.writeSplitSint64(fieldNumber, lowBits, highBits);
// Example: Writing message fields
writer.writeString(1, message.getName());
writer.writeInt32(2, message.getAge());
writer.writeBool(3, message.getActive());
writer.writeEnum(4, message.getStatus());// 32-bit fixed
writer.writeFixed32(fieldNumber, fixed32Value);
writer.writeSfixed32(fieldNumber, sfixed32Value);
writer.writeFloat(fieldNumber, floatValue);
// 64-bit fixed
writer.writeFixed64(fieldNumber, fixed64Value);
writer.writeSfixed64(fieldNumber, sfixed64Value);
writer.writeDouble(fieldNumber, doubleValue);
// Split 64-bit writes
writer.writeSplitFixed64(fieldNumber, lowBits, highBits);
writer.writeSplitSfixed64(fieldNumber, lowBits, highBits);
// Example: Writing numeric data
writer.writeFloat(1, temperature);
writer.writeDouble(2, coordinates.latitude);
writer.writeFixed64(3, timestamp);// String and bytes
writer.writeString(fieldNumber, stringValue);
writer.writeBytes(fieldNumber, bytesValue);
// Messages (nested)
writer.writeMessage(fieldNumber, message, serializeFn);
// Pre-serialized messages
writer.writeSerializedMessage(serializedBytes, start, end);
// Group fields (deprecated)
writer.writeGroup(fieldNumber, message, serializeFn);
// Example: Writing complex fields
writer.writeString(1, "User Name");
writer.writeBytes(2, profilePicture);
// Write nested message
writer.writeMessage(3, address, (msg, writer) => {
writer.writeString(1, msg.getStreet());
writer.writeString(2, msg.getCity());
});const { BinaryWriter } = require('google-protobuf');
const writer = new BinaryWriter();
// Repeated fields (write multiple times with same field number)
const names = ["Alice", "Bob", "Charlie"];
names.forEach(name => {
writer.writeString(1, name);
});
// Packed repeated fields (more efficient for numeric types)
const numbers = [1, 2, 3, 4, 5];
writer.writePackedInt32(2, numbers);
// Other packed types
writer.writePackedUint32(3, uint32Array);
writer.writePackedBool(4, boolArray);
writer.writePackedFloat(5, floatArray);
writer.writePackedDouble(6, doubleArray);
writer.writePackedFixed32(7, fixed32Array);
writer.writePackedFixed64(8, fixed64Array);
// ... other packed types available
// Example: Efficient numeric array serialization
const measurements = [1.1, 2.2, 3.3, 4.4, 5.5];
writer.writePackedFloat(1, measurements);
const timestamps = [100n, 200n, 300n, 400n];
writer.writePackedUint64(2, timestamps);const { BinaryWriter } = require('google-protobuf');
const writer = new BinaryWriter();
// Write any field type dynamically
const fieldType = 9; // STRING type
writer.writeAny(fieldType, fieldNumber, value);
// Write unknown fields (preserves unrecognized data)
writer.writeUnknownFields(unknownFieldsMap);
// Direct buffer writing for pre-serialized data
writer.writeSerializedMessage(preSerializedBytes, 0, preSerializedBytes.length);
// Example: Dynamic field writing
const fields = [
{ type: 9, number: 1, value: "string value" }, // STRING
{ type: 5, number: 2, value: 42 }, // INT32
{ type: 8, number: 3, value: true } // BOOL
];
fields.forEach(field => {
writer.writeAny(field.type, field.number, field.value);
});
const result = writer.getResultBuffer();const { BinaryConstants } = require('google-protobuf');
// Field types
const fieldTypes = {
INVALID: BinaryConstants.FieldType.INVALID, // -1
DOUBLE: BinaryConstants.FieldType.DOUBLE, // 1
FLOAT: BinaryConstants.FieldType.FLOAT, // 2
INT64: BinaryConstants.FieldType.INT64, // 3
UINT64: BinaryConstants.FieldType.UINT64, // 4
INT32: BinaryConstants.FieldType.INT32, // 5
FIXED64: BinaryConstants.FieldType.FIXED64, // 6
FIXED32: BinaryConstants.FieldType.FIXED32, // 7
BOOL: BinaryConstants.FieldType.BOOL, // 8
STRING: BinaryConstants.FieldType.STRING, // 9
MESSAGE: BinaryConstants.FieldType.MESSAGE, // 11
BYTES: BinaryConstants.FieldType.BYTES, // 12
UINT32: BinaryConstants.FieldType.UINT32, // 13
ENUM: BinaryConstants.FieldType.ENUM, // 14
SFIXED32: BinaryConstants.FieldType.SFIXED32, // 15
SFIXED64: BinaryConstants.FieldType.SFIXED64, // 16
SINT32: BinaryConstants.FieldType.SINT32, // 17
SINT64: BinaryConstants.FieldType.SINT64 // 18
};
// Wire types
const wireTypes = {
INVALID: BinaryConstants.WireType.INVALID, // -1
VARINT: BinaryConstants.WireType.VARINT, // 0
FIXED64: BinaryConstants.WireType.FIXED64, // 1
DELIMITED: BinaryConstants.WireType.DELIMITED, // 2
FIXED32: BinaryConstants.WireType.FIXED32 // 5
};
// Utility functions
const wireType = BinaryConstants.FieldTypeToWireType(fieldType);
const isValidWire = BinaryConstants.isValidWireType(wireType);
// Constants
const invalidField = BinaryConstants.INVALID_FIELD_NUMBER; // -1
const invalidTag = BinaryConstants.INVALID_TAG; // -1Type Definitions:
/**
* @enum {number} Field types for Protocol Buffer fields
*/
/**
* @enum {number} Wire types for Protocol Buffer encoding
*/For complex binary operations, use the high-level BinaryReader and BinaryWriter classes which provide comprehensive wire format support:
const { BinaryReader, BinaryWriter } = require('google-protobuf');
// Complex message parsing
function parseComplexMessage(bytes) {
const reader = new BinaryReader(bytes);
const result = {};
while (reader.nextField()) {
const fieldNumber = reader.getFieldNumber();
const wireType = reader.getWireType();
// Handle different field types based on wire type
switch (wireType) {
case 0: // VARINT
result[fieldNumber] = reader.readUint64();
break;
case 1: // FIXED64
result[fieldNumber] = reader.readDouble();
break;
case 2: // DELIMITED
result[fieldNumber] = reader.readString();
break;
case 5: // FIXED32
result[fieldNumber] = reader.readFloat();
break;
}
}
return result;
}
// Complex message generation
function generateComplexMessage(data) {
const writer = new BinaryWriter();
Object.entries(data).forEach(([fieldNum, value]) => {
const fieldNumber = parseInt(fieldNum);
if (typeof value === 'string') {
writer.writeString(fieldNumber, value);
} else if (typeof value === 'number') {
writer.writeDouble(fieldNumber, value);
}
});
return writer.getResultBuffer();
}// Use allocation cache for frequently created readers
const reader = BinaryReader.alloc(bytes); // From cache
// ... use reader
reader.free(); // Return to cache
// Configure reader options for performance
const options = {
aliasBytesFields: true, // Avoid copying bytes (careful with mutability)
discardUnknownFields: true // Skip unknown fields entirely
};
const reader = new BinaryReader(bytes, 0, bytes.length, options);// Reuse writer instances
const writer = new BinaryWriter();
function serializeMessage(message) {
writer.reset(); // Clear previous data
// Write fields
writer.writeString(1, message.name);
writer.writeInt32(2, message.value);
return writer.getResultBuffer();
}
// Multiple serializations with same writer
const results = messages.map(serializeMessage);// Efficient buffer handling
const reader = new BinaryReader(buffer);
// Avoid creating unnecessary intermediate objects
while (reader.nextField()) {
const fieldNum = reader.getFieldNumber();
// Process fields directly without intermediate storage
switch (fieldNum) {
case 1:
processString(reader.readString());
break;
case 2:
processNumber(reader.readInt32());
break;
}
}