Skip to content

vedadiyan/protolizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

42 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Protolizer

A zero-dependency, reflection-based Protocol Buffers library for Go that enables dynamic serialization and deserialization without requiring generated code or proto.reflect.

✨ Features

  • 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]any for 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

πŸš€ Installation

go get github.com/vedadiyan/protolizer

πŸ“– Quick Start

Basic Usage with Structs

package 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) }

Dynamic Map-Based Usage

// 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)

πŸ—οΈ Advanced Usage

Complex Types

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]()

Supported Field Types

Primitive Types

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"` }

Collections

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"` }

Schema Export/Import

// 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) }

🏷️ Protobuf Tag Format

Protolizer uses standard protobuf struct tags with the following format:

`protobuf:"<wire_type>,<field_number>,<label>,name=<field_name>,<syntax>"`

Wire Types

  • 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)

Labels

  • opt - Optional field
  • req - Required field (proto2)
  • rep - Repeated field

Map Fields

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"`

🎯 API Reference

Core Functions

RegisterTypeFor[T any]()

Registers a type in the global type registry for dynamic serialization.

Marshal(v any) ([]byte, error)

Serializes a Go struct to protobuf wire format.

Unmarshal(bytes []byte, v any) error

Deserializes protobuf bytes into a Go struct.

Read(typeName string, bytes []byte) (map[string]any, error)

Converts protobuf bytes to a map for dynamic inspection/manipulation.

Write(typeName string, v map[string]any) ([]byte, error)

Converts a map back to protobuf bytes.

Type Introspection

CaptureTypeFor[T any]() *Type

Returns type information for a registered type.

CaptureType(t reflect.Type) *Type

Returns type information for a reflect.Type.

CaptureTypeByName(typeName string) *Type

Returns type information by type name.

Schema Export/Import

ExportType[T any]() ([]byte, error)

Exports a single type's schema as protobuf bytes.

ImportType(bytes []byte) (*Type, error)

Imports a type schema from protobuf bytes.

ExportModule[T any]() ([]byte, error)

Exports all related types as a module.

ImportModule(bytes []byte) (*Module, error)

Imports a complete module with all types.

πŸ”§ Wire Format Details

Protolizer implements the complete Protocol Buffers wire format specification:

Encoding Rules

  • 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

Tag Format

Each field is prefixed with a tag containing:

  • Field number (bits 3+)
  • Wire type (bits 0-2)

⚑ Performance Considerations

  • 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

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Setup

# Clone the repo git clone https://github.com/vedadiyan/protolizer.git cd protolizer # Run tests go test ./... # Run benchmarks go test -bench=. ./...

πŸ“‹ Limitations

  • 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

πŸ“„ License

This project is licensed under the Apache 2 License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • 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

About

A Zero-Dependency Protobuffer Serializer

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages