A zero-dependency, reflection-based Protocol Buffers library for Go that enables dynamic serialization and deserialization without requiring generated code or proto.reflect.
- Zero Dependencies: No external dependencies beyond Go's standard library
- Dynamic Serialization: Serialize/deserialize protobuf messages without generated Go code
- Reflection-Based: Uses Go's reflection to introspect struct tags and types
- Map Conversion: Convert protobuf messages to
map[string]anyfor inspection and manipulation - Type Registry: Built-in type registration and schema export/import
- Wire Format Compliant: Full support for all protobuf wire types and encoding rules
- No proto.reflect: Independent implementation that doesn't rely on Google's protobuf-go
go get github.com/vedadiyan/protolizerpackage main
import (
"fmt"
"github.com/vedadiyan/protolizer"
)
type Person struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3"`
Email string `protobuf:"bytes,3,opt,name=email,proto3"`
}
func main() {
// Register the type
protolizer.RegisterTypeFor[Person]()
// Create a person
person := Person{
Name: "John Doe",
Age: 30,
Email: "john@example.com",
}
// Marshal to protobuf bytes
data, err := protolizer.Marshal(&person)
if err != nil {
panic(err)
}
// Unmarshal back to struct
var decoded Person
err = protolizer.Unmarshal(data, &decoded)
if err != nil {
panic(err)
}
fmt.Printf("Original: %+v\n", person)
fmt.Printf("Decoded: %+v\n", decoded)
}// Convert protobuf bytes to map for inspection
personMap, err := protolizer.Read("main.Person", data)
if err != nil {
panic(err)
}
fmt.Printf("As map: %+v\n", personMap)
// Modify the map
personMap["Age"] = float64(31)
personMap["Email"] = "john.doe@example.com"
// Convert map back to protobuf bytes
newData, err := protolizer.Write("main.Person", personMap)
if err != nil {
panic(err)
}
// Unmarshal the modified data
var modifiedPerson Person
err = protolizer.Unmarshal(newData, &modifiedPerson)
if err != nil {
panic(err)
}
fmt.Printf("Modified: %+v\n", modifiedPerson)type Address struct {
Street string `protobuf:"bytes,1,opt,name=street,proto3"`
City string `protobuf:"bytes,2,opt,name=city,proto3"`
Country string `protobuf:"bytes,3,opt,name=country,proto3"`
}
type Contact struct {
Person Person `protobuf:"bytes,1,opt,name=person,proto3"`
Address *Address `protobuf:"bytes,2,opt,name=address,proto3"`
Phones []string `protobuf:"bytes,3,rep,name=phones,proto3"`
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}
// Register all types
protolizer.RegisterTypeFor[Address]()
protolizer.RegisterTypeFor[Contact]()type Primitives struct {
// Integer types
Int32Field int32 `protobuf:"varint,1,opt,name=int32_field,proto3"`
Int64Field int64 `protobuf:"varint,2,opt,name=int64_field,proto3"`
Uint32Field uint32 `protobuf:"varint,3,opt,name=uint32_field,proto3"`
Uint64Field uint64 `protobuf:"varint,4,opt,name=uint64_field,proto3"`
// Fixed-width types
Fixed32 uint32 `protobuf:"fixed32,5,opt,name=fixed32,proto3"`
Fixed64 uint64 `protobuf:"fixed64,6,opt,name=fixed64,proto3"`
Sfixed32 int32 `protobuf:"fixed32,7,opt,name=sfixed32,proto3"`
Sfixed64 int64 `protobuf:"fixed64,8,opt,name=sfixed64,proto3"`
// Float types
FloatField float32 `protobuf:"fixed32,9,opt,name=float_field,proto3"`
DoubleField float64 `protobuf:"fixed64,10,opt,name=double_field,proto3"`
// String and bytes
StringField string `protobuf:"bytes,11,opt,name=string_field,proto3"`
BytesField []byte `protobuf:"bytes,12,opt,name=bytes_field,proto3"`
// Boolean
BoolField bool `protobuf:"varint,13,opt,name=bool_field,proto3"`
}type Collections struct {
// Repeated fields
Numbers []int32 `protobuf:"varint,1,rep,packed,name=numbers,proto3"`
Names []string `protobuf:"bytes,2,rep,name=names,proto3"`
// Maps
StringMap map[string]string `protobuf:"bytes,3,rep,name=string_map,proto3" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
IntMap map[int32]string `protobuf:"bytes,4,rep,name=int_map,proto3" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}// Export type schema
schemaBytes, err := protolizer.ExportType[Person]()
if err != nil {
panic(err)
}
// Import type schema
importedType, err := protolizer.ImportType(schemaBytes)
if err != nil {
panic(err)
}
// Export entire module (all related types)
moduleBytes, err := protolizer.ExportModule[Contact]()
if err != nil {
panic(err)
}
// Import module
module, err := protolizer.ImportModule(moduleBytes)
if err != nil {
panic(err)
}Protolizer uses standard protobuf struct tags with the following format:
`protobuf:"<wire_type>,<field_number>,<label>,name=<field_name>,<syntax>"`varint- Variable-length integers (int32, int64, uint32, uint64, bool)fixed64- Fixed 64-bit values (double, fixed64, sfixed64)bytes- Length-delimited (string, bytes, messages, packed repeated)fixed32- Fixed 32-bit values (float, fixed32, sfixed32)
opt- Optional fieldreq- Required field (proto2)rep- Repeated field
For map fields, specify key and value wire types:
MapField map[string]int32 `protobuf:"bytes,1,rep,name=map_field,proto3" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`Registers a type in the global type registry for dynamic serialization.
Serializes a Go struct to protobuf wire format.
Deserializes protobuf bytes into a Go struct.
Converts protobuf bytes to a map for dynamic inspection/manipulation.
Converts a map back to protobuf bytes.
Returns type information for a registered type.
Returns type information for a reflect.Type.
Returns type information by type name.
Exports a single type's schema as protobuf bytes.
Imports a type schema from protobuf bytes.
Exports all related types as a module.
Imports a complete module with all types.
Protolizer implements the complete Protocol Buffers wire format specification:
- Varints: Variable-length encoding for integers
- Fixed32/64: Little-endian fixed-width encoding
- Length-Delimited: Length-prefixed encoding for strings, bytes, and messages
- Packed Repeated: Efficient encoding for repeated numeric fields
Each field is prefixed with a tag containing:
- Field number (bits 3+)
- Wire type (bits 0-2)
- Reflection Overhead: Uses reflection for type introspection, which has some performance cost
- Memory Allocation: Creates temporary objects during marshaling/unmarshaling
- Type Registration: Types should be registered once at startup, not per operation
- Large Messages: For very large messages, consider streaming approaches
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone the repo
git clone https://github.com/vedadiyan/protolizer.git
cd protolizer
# Run tests
go test ./...
# Run benchmarks
go test -bench=. ./...- No Proto Files: Does not parse .proto files directly (struct tags define schema)
- No Code Generation: Requires manual struct tag annotation
- Reflection Required: Cannot eliminate reflection for type safety
- Go-Specific: Designed specifically for Go, not cross-language compatible without schema export
This project is licensed under the Apache 2 License - see the LICENSE file for details.
- Protocol Buffers specification by Google
- Go reflection and type system
- The Go community for inspiration and best practices
Made with β€οΈ for dynamic protobuf handling in Go