A powerful Go library for working with Protocol Buffers without generated code. Protolite provides schema-less parsing, schema-based marshaling/unmarshaling, and automatic Go struct mapping with reflection.
- π Schema-less Parsing - Inspect any protobuf data without knowing the schema
- π Schema-based Operations - Marshal/unmarshal with
.protofile schemas - ποΈ Automatic Struct Mapping - Populate Go structs using reflection
- π― Wire Format Support - All protobuf wire types (varint, fixed32, fixed64, bytes)
- π Type Safety - Proper Go type conversions with error handling
- π Nested Messages - Support for recursive and complex message structures
go get github.com/anirudhraja/protolitepackage main
import (
"fmt"
"github.com/anirudhraja/protolite"
)
func main() {
// Create Protolite instance
proto := protolite.NewProtolite()
// Schema-less parsing (no .proto file needed)
result, err := proto.Parse(protobufData)
if err != nil {
panic(err)
}
fmt.Printf("Unknown protobuf contains: %+v\n", result)
// Schema-based operations (requires .proto file)
err = proto.LoadSchemaFromFile("user.proto")
if err != nil {
panic(err)
}
// Unmarshal to map
userMap, err := proto.UnmarshalWithSchema(protobufData, "User")
fmt.Printf("User data: %+v\n", userMap)
// Unmarshal to Go struct
var user User
err = proto.UnmarshalToStruct(protobufData, "User", &user)
fmt.Printf("User struct: %+v\n", user)
}type Protolite interface {
// Schema-less parsing
Parse(data []byte) (map[string]interface{}, error)
// Schema-based operations
LoadSchemaFromFile(protoPath string) error
MarshalWithSchema(data map[string]interface{}, messageName string) ([]byte, error)
UnmarshalWithSchema(data []byte, messageName string) (map[string]interface{}, error)
UnmarshalToStruct(data []byte, messageName string, v interface{}) error
}| Method | Schema Required | Output Type | Field Keys | Use Case |
|---|---|---|---|---|
Parse |
β No | map[string]interface{} |
field_1, field_2 |
Debug unknown protobuf |
UnmarshalWithSchema |
β Yes | map[string]interface{} |
id, name, email |
Dynamic processing |
UnmarshalToStruct |
β Yes | Go struct | Struct fields | Type-safe application code |
When to use: Debug unknown protobuf data, inspect wire format, reverse engineering.
proto := protolite.NewProtolite()
// Parse any protobuf data without schema
result, err := proto.Parse(unknownProtobufData)
if err != nil {
log.Fatal(err)
}
// Output shows wire format structure
fmt.Printf("Parsed: %+v\n", result)
// Output: map[field_1:map[type:varint value:123] field_2:map[type:bytes value:[104 101 108 108 111]]]Output format:
map[string]interface{}{
"field_1": map[string]interface{}{
"type": "varint", // Wire type: varint, fixed32, fixed64, bytes
"value": uint64(123), // Raw decoded value
},
"field_2": map[string]interface{}{
"type": "bytes",
"value": []byte("hello"),
},
}When to use: Dynamic processing, JSON conversion, generic data handling.
proto := protolite.NewProtolite()
// Load schema first
err := proto.LoadSchemaFromFile("schemas/user.proto")
if err != nil {
log.Fatal(err)
}
// Unmarshal with proper field names and types
userMap, err := proto.UnmarshalWithSchema(protobufData, "User")
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %+v\n", userMap)
// Output: map[id:123 name:John Doe email:john@example.com active:true]
// Convert to JSON easily
jsonData, _ := json.Marshal(userMap)
fmt.Printf("JSON: %s\n", jsonData)When to use: Type-safe application code, direct struct usage, compile-time safety.
type User struct {
ID int32 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active"`
}
proto := protolite.NewProtolite()
err := proto.LoadSchemaFromFile("schemas/user.proto")
if err != nil {
log.Fatal(err)
}
// Direct struct population with reflection
var user User
err = proto.UnmarshalToStruct(protobufData, "User", &user)
if err != nil {
log.Fatal(err)
}
// Use struct fields directly with type safety
fmt.Printf("User ID: %d, Name: %s, Active: %t\n", user.ID, user.Name, user.Active)Smart Field Matching:
IDβ matchesidorIDUserNameβ matchesuser_name,username,UserNameEmailAddressβ matchesemail_address,EmailAddress
proto := protolite.NewProtolite()
err := proto.LoadSchemaFromFile("schemas/user.proto")
// Create data to marshal
userData := map[string]interface{}{
"id": int32(456),
"name": "Jane Smith",
"email": "jane@example.com",
"active": true,
}
// Marshal to protobuf bytes
protobufData, err := proto.MarshalWithSchema(userData, "User")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Encoded %d bytes\n", len(protobufData))proto := protolite.NewProtolite()
err := proto.LoadSchemaFromFile("schemas/user.proto")
// Wrapper types allow null/unset values (unlike regular proto3 primitives)
userData := map[string]interface{}{
"id": int32(123),
"optional_name": "John Doe", // google.protobuf.StringValue
"optional_age": int32(30), // google.protobuf.Int32Value
"optional_score": nil, // Unset wrapper field (won't be encoded)
}
// Marshal with wrapper types
protobufData, err := proto.MarshalWithSchema(userData, "User")
// Unmarshal preserves null semantics
result, err := proto.UnmarshalWithSchema(protobufData, "User")
// result["optional_score"] will be nil (not default value)Supported Wrapper Types:
google.protobuf.StringValue,google.protobuf.BytesValuegoogle.protobuf.Int32Value,google.protobuf.Int64Valuegoogle.protobuf.UInt32Value,google.protobuf.UInt64Valuegoogle.protobuf.BoolValue,google.protobuf.FloatValue,google.protobuf.DoubleValue
- β
int32,int64,uint32,uint64 - β
bool,string,bytes - β
float,double - β
enumvalues
- β Nested Messages - Recursive message structures
- β
Maps -
map<string, int32>,map<string, string>, etc. - β Enums - Named constants with validation
- β Repeated Fields - Arrays and lists
- β Oneof Fields - Union types for mutually exclusive fields
- β Wrapper Types - Google protobuf wrappers (StringValue, Int32Value, etc.)
- β Varint - Variable-length integers
- β Fixed32 - 4-byte fixed-width (float, fixed32, sfixed32)
- β Fixed64 - 8-byte fixed-width (double, fixed64, sfixed64)
- β Bytes - Length-delimited (string, bytes, messages)
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Protolite β β Wire Format β β Schema β
β Interface ββββββ Decoders ββββββ Registry β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β
β βββββββββββββββββββ β
ββββββββββββββββ Reflection ββββββββββββββββ
β Engine β
βββββββββββββββββββ
- π― Protolite Interface - High-level API for users
- π§ Wire Format Decoders - Low-level protobuf parsing (varint, fixed, bytes)
- π Schema Registry -
.protofile loading and message definitions - πͺ Reflection Engine - Go struct mapping with type conversion
Comparison of unmarshalling in different approaches:
Basic message with primitive fields (id, name, email, active status).
| Method | Time (ns/op) | Memory (B/op) | Allocs/op |
|---|---|---|---|
| Protolite | 919.5 | 440 | 10 |
| Protoc (generated) | 436.8 | 232 | 4 |
| DynamicPB (static) | 973.1 | 576 | 11 |
| DynamicPB (runtime) | 945.7 | 632 | 13 |
Nested message with maps, repeated fields, oneofs, and enums.
| Method | Time (ns/op) | Memory (B/op) | Allocs/op |
|---|---|---|---|
| Protolite | 1,183 | 440 | 10 |
| Protoc (generated) | 4,232 | 3,536 | 102 |
| DynamicPB (static) | 2,129 | 2,784 | 16 |
| DynamicPB (runtime) | 20,956 | 9,632 | 177 |
Note: Benchmarks run with 100K iterations on Apple M2 Pro, Go 1.21
Run the comprehensive test suite:
# Run all tests
go test -v ./...
# Run specific component tests
go test -v ./wire # Wire format tests
go test -v ./registry # Schema registry tests
go test -v . # API tests
# Run with coverage
go test -cover ./...- β All primitive types - Complete wire format coverage
- β Nested messages - Recursive structures
- β Maps and enums - Complex type support
- β Edge cases - Empty messages, zero values, extreme values
- β Error handling - Invalid data, missing schemas
Check out the comprehensive sample app for advanced usage examples:
cd sampleapp/
go run main.goThe sample app demonstrates all protobuf features including oneof, nested messages, maps, enums, and recursive structures!
- β Services/RPC - No gRPC service definitions or method calls
- β Custom Options - Proto file custom options not parsed
- β Import Public -
import publicstatements not handled
- β google.protobuf.Any - Type erasure/dynamic types
- β google.protobuf.Timestamp - No automatic time conversion
- β google.protobuf.Duration - No automatic duration parsing
- β google.protobuf.Wrapper - Value wrapper types (StringValue, Int32Value, etc.)
- β google.protobuf.FieldMask - Field selection masks
- β google.protobuf.Struct - Dynamic JSON-like structures
- β google.protobuf.Empty - Empty message type
- β Zero-Copy Parsing - All data is copied during parsing
- β Lazy Loading - No lazy field evaluation
- β Memory Pooling - No object reuse or memory pools
- β Streaming Parser - Must load entire message into memory
- Runtime schema loading - Slightly slower than generated code for simple data
- Reflection overhead - Struct mapping uses reflection for flexibility
- Focus on Proto3 - Full proto3 support, limited proto2 features
- Simple .proto parsing - Basic proto file parsing, not full protoc compatibility
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.