plenc

package module
v0.0.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 5, 2024 License: MIT Imports: 7 Imported by: 4

README

plenc

GoDoc

plenc is a serialisation library based around protobuf. It uses a very similar encoding to protobuf, but it does not use .proto files or the protobuf data definition language. Instead Go structs are used to define how messages are encoded.

plenc needs you to annotate your structs with a plenc tag on each field. The tag either indicates that the field should not be encoded, or provides a persistent index number that's used for that field in the encoding. The indexes within a struct must all be unique and should not be changed. You may remove fields, but you should not re-use the index number of a removed field. You should not change the type of a field. You can change field names as these are not used in the encoding.

Tags look like the following.

type mystruct struct {
	A  int     `plenc:"1"`
	B  string  `plenc:"-"` // This field is not encoded
	C  float64 `plenc:"2"`
	// The values of this field are interned. This reduces allocations if
	// there are a limited set of distinct values used.
	D  string  `plenc:"3,intern"`
}

The plenctag tool will add tags to structs for you.

plenc only encodes fields that are exported - ones where the field name begins with a capital letter.

Once you've added plenc tags to your structs then encoding and decoding looks very like the JSON standard library. The one difference is that the Marshal function allows you to append encoded data to an existing slice.

var in mystruct 
data, err := plenc.Marshal(data[:0], &in)
if err != nil {
	return err
}

var out mystruct
if err := plenc.Unmarshal(data, &out); err != nil {
	return err
}

Why do this?

The idea behind plenc is to unlock the performance of protobuf for folk who don't like the Go structs generated by the protobuf compiler and don't want the hassle of creating .proto files. It is for people who want to retrofit better serialisation to a system that's started with JSON.

Here's a rough benchmark to show the kind of gains you could get using plenc.

BenchmarkCycle/plenc-16    1369533     881 ns/op    1400 B/op     38 allocs/op
BenchmarkCycle/json-16      214154    5620 ns/op    5211 B/op    120 allocs/op

Is this protobuf?

No, as it stands it is not quite protobuf. It is largely protobuf and has a soft aim to at least be able to read standard protobuf, but there are differences.

The big difference is that plenc uses its own encoding for slices of types that are implemented with WTLength. Plenc introduces a new wire-type for these - WTSlice (3). The Tag byte is followed by a unsigned varint containing the number of elements in the slice, then each element is encoded with its length as an unsigned varint then the element encoding. This encoding means the receiver easily knows the length of the slice and can allocate it in a single operation.

You can encode slices with the standard protobuf encoding by using a Plenc object and setting the ProtoCompatibleSlices option. With this option set plenc will use the standard protobuf encoding for slices.

Plenc is able to read most protobuf data encoded with simple types and standard encodings. It isn't currently able to read proto encoded maps, for example.

Also using fixed32 and fixed64 encodings for integer types is not currently supported. I think we could support that via an option on the plenc tag that would select a different codec.

Slices

Neither plenc nor protobuf distinuguish between empty and nil slices. If you write an empty slice it will read back as a nil slice.

Slices of pointers to floats are not allowed.

Nils within slices of pointers are not supported. Nils in slices of pointer to integers will be omitted. Nils in slices of pointers to structs will be converted to empty structs.

Documentation

Overview

Package plenc provides an efficient serialisation protocol based on protobuf, but without requiring .proto files or awkward autogenerated structs. Your structs are the basis for the serialisation.

plenc needs you to annotate your structs with a plenc tag on each field. The tag either indicates that the field should not be encoded, or provides a persistent index number that's used for that field in the encoding. The indexes within a struct must all be unique and should not be changed. You may remove fields, but you should not re-use the index number of a removed field. You should not change the type of a field. You can change field names as these are not used in the encoding.

Tags look like the following.

type mystruct struct {
    A  int `plenc:"1"`
    B  string `plenc:"-"` // This field is not encoded
    C  float64 `plenc:"2"`
    // The values of this field are interned. This reduces allocations if
    // there are a limited set of distinct values used.
    D  string `plenc:"3,intern"`
}

The plenctag tool will add tags to structs for you.

plenc only encodes fields that are exported - ones where the field name begins with a capital letter.

Once your structs have plenc tags, encoding and decoding data is very much like the standard JSON library using Marshal and Unmarshal calls. The one difference is that the Marshal function allows you to append encoded data to an existing slice.

Example
package main

import (
	"fmt"

	"github.com/philpearl/plenc"
)

func main() {
	type Hat struct {
		Type string  `plenc:"1"`
		Size float32 `plenc:"2"`
	}

	type Person struct {
		Name string `plenc:"1"`
		Age  int    `plenc:"2"`
		Hats []Hat  `plenc:"3"`
	}

	var me = Person{
		Name: "Lucy",
		Age:  25,
		Hats: []Hat{
			{Type: "Fedora", Size: 6},
			{Type: "floppy", Size: 5.5},
		},
	}

	data, err := plenc.Marshal(nil, &me)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("%X\n", data)

	var out Person
	if err := plenc.Unmarshal(data, &out); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("%#v\n", out)

}
Output:

0A044C75637910321B020D0A064665646F7261150000C0400D0A06666C6F707079150000B040
plenc_test.Person{Name:"Lucy", Age:25, Hats:[]plenc_test.Hat{plenc_test.Hat{Type:"Fedora", Size:6}, plenc_test.Hat{Type:"floppy", Size:5.5}}}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CodecForType

func CodecForType(typ reflect.Type) (plenccodec.Codec, error)

CodecForType returns a codec for the requested type. It should only be needed when constructing a codec based on an existing plenc codec

func CodecForTypeWithTag added in v0.0.12

func CodecForTypeWithTag(typ reflect.Type, tag string) (plenccodec.Codec, error)

CodecForTypeWithTag returns a codec for the requested type and tag. See RegisterCodecWithTag.

func Marshal

func Marshal(data []byte, value interface{}) ([]byte, error)

func RegisterCodec

func RegisterCodec(typ reflect.Type, c plenccodec.Codec)

RegisterCodec registers a codec with plenc so it can be used for marshaling and unmarshaling. If you write a custom codec then you need to register it before it can be used.

func RegisterCodecWithTag added in v0.0.12

func RegisterCodecWithTag(typ reflect.Type, tag string, c plenccodec.Codec)

RegisterCodecWithTag is like RegisterCodec, but it registers the codec to be used only when the given tag is specified in the plenc struct tag

For example:

type MyStruct struct {
	A int `plenc:"1"`
	B int `plenc:"2,flat"`
}

The default codecs include a flat codec for ints. This does not use the ZigZag encoding as is more efficient when you know the values are positive.

func Unmarshal

func Unmarshal(data []byte, value interface{}) error

Types

type Plenc

type Plenc struct {
	// Plenc's original time handling was not compatible with protobuf's
	// google.protobuf.Timestamp. Set this to true to enable a proto compatible
	// time codec. Set it before calling RegisterDefaultCodecs.
	ProtoCompatibleTime bool
	// ProtoCompatibleArrays controls how plenc handles slices and arrays of
	// data. When set to true Plenc writes arrays that are compatible with
	// protobuf. If not true it uses a format that allows arrays to be read more
	// efficiently. Set it before calling RegisterDefaultCodecs.
	ProtoCompatibleArrays bool
	// contains filtered or unexported fields
}

Plenc is an instance of the plenc encoding and decoding system. It contains a registry of plenc codecs. Use it if you need fine-grain control on plenc's behaviour.

var p plenc.Plenc
p.RegisterDefaultCodecs()
data, err := p.Marshal(nil, mystruct)

func (*Plenc) CodecForType added in v0.0.4

func (p *Plenc) CodecForType(typ reflect.Type) (plenccodec.Codec, error)

CodecForType finds an existing codec for a type or constructs a codec. It calls CodecForTypeRegistry using the internal registry on p

func (*Plenc) CodecForTypeRegistry added in v0.0.5

func (p *Plenc) CodecForTypeRegistry(registry plenccodec.CodecRegistry, typ reflect.Type, tag string) (plenccodec.Codec, error)

CodecForTypeRegistry builds a new codec for the requested type, consulting registry for any existing codecs needed

func (*Plenc) CodecForTypeWithTag added in v0.0.12

func (p *Plenc) CodecForTypeWithTag(typ reflect.Type, tag string) (plenccodec.Codec, error)

func (*Plenc) Marshal

func (p *Plenc) Marshal(data []byte, value interface{}) ([]byte, error)

func (*Plenc) RegisterCodec

func (p *Plenc) RegisterCodec(typ reflect.Type, c plenccodec.Codec)

func (*Plenc) RegisterCodecWithTag added in v0.0.12

func (p *Plenc) RegisterCodecWithTag(typ reflect.Type, tag string, c plenccodec.Codec)

func (*Plenc) RegisterDefaultCodecs

func (p *Plenc) RegisterDefaultCodecs()

RegisterDefaultCodecs sets up the default codecs for plenc. It is called automatically for the default plenc instance, but if you create your own instance of Plenc you should call this before using it.

func (*Plenc) Unmarshal

func (p *Plenc) Unmarshal(data []byte, value interface{}) error

Directories

Path Synopsis
cmd
plenctag
plenctag adds plenc tags to your structs
plenctag adds plenc tags to your structs
Package null contains plenc codecs for the types in github.com/unravelin/null.
Package null contains plenc codecs for the types in github.com/unravelin/null.
Package plenccodec provides the core Codecs for plenc.
Package plenccodec provides the core Codecs for plenc.
Package plenccore describes the wire protocol used by plenc.
Package plenccore describes the wire protocol used by plenc.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL