Golang encoder/decoder from io.ReadSeeker and io.Writer
Please note this is not a very rigorous way to decode a io.ReadSeeker/Writer.
I use io.ReadSeeker over io.Reader so the package can know the position of a failure.
To obtain, just run go get github.com/xackery/encdec
- Perk: Github copilot works very smoothly with this approach. Define a struct, get decoder initialized, and watch as copilot fills all the decoding fields one by one, even the subStruct example below was filled with copilot. Same flow for encoder.
- Perk: Easy to read and modify later. Most fields are a single line, making it easy to identify and fix later.
- Perk: Don't need to expose properties in a struct. Public (uppercase) is optional
- Perk: No reflection used, no struct tags needed
- Perk: Easy to lace in conditional values for variable binary streams
- Con: Not always super intuitive where a failure occurred, since no context of which property failed like with binary.Read/Write
- Con: Always sanitize default value cases, or a panic may occurr with returned values
Example usage (can be seen as a test here)
import (
"encoding/binary"
"github.com/xackery/encdec"
"fmt"
)
type exampleStruct struct {
val1 int16
val2 uint32
someStr1 string
someStr2 string
someStrZero string
val3 float32
someBytes []byte
someSubStructs []exampleSubStruct
}
type exampleSubStruct struct {
val1 bool
val2 float64
val3 exampleVector3
}
type exampleVector3 struct {
x float32
y float32
z float32
}
func someReadSeekerExample(r io.ReadSeeker) (*exampleStruct, error) {
dec := NewDecoder(r, binary.LittleEndian)
def := &exampleStruct{} // initialize an example struct in def
def.val1 = dec.Int16() // decode int16 worth of bytes to val1
def.val2 = dec.Uint32() // decode uint16 worth of bytes to val2
def.someStr1 = dec.StringFixed(3) // read 3 bytes and convert to a string
def.someStr2 = dec.StringLenPrefixUint32() // read 4 bytes (uint32) to sort length of string, then read length and convert to string
def.someStrZero = dec.StringZero() // read until 0x00 and convert to string
def.val3 = dec.Float32() // read 4 bytes and convertt float and place into val3
def.someBytes = dec.Bytes(4) // read 4 bytes and place into someBytes
subStructLen := dec.Uint32() // read 4 bytes (uint32) to sort length of sub struct
for i := 0; i < int(subStructLen); i++ {
subStruct := exampleSubStruct{}
subStruct.val1 = dec.Bool() // read 1 byte and convert to bool
subStruct.val2 = dec.Float64() // read 8 bytes and convert to float64
subStruct.val3.x = dec.Float32() // read 4 bytes and convert to float32
subStruct.val3.y = dec.Float32() // read 4 bytes and convert to float32
subStruct.val3.z = dec.Float32() // read 4 bytes and convert to float32
def.someSubStructs = append(def.someSubStructs, subStruct) // append sub struct to someSubStructs
}
// all error handling can be checked at end like this:
if dec.Error() != nil {
return nil, fmt.Errorf("decode: %w", dec.Error())
}
return def, nil
}
func (def *exampleStruct) someWriterExample(w io.Writer) error {
enc := NewEncoder(w, binary.LittleEndian)
enc.Int16(def.val1) // encode val1 as int16
enc.Uint32(def.val2) // encode val2 as uint32
enc.StringZero(def.someStrZero) // write string until 0x00
enc.StringFixed(def.someStr1, 3) // write 3 bytes of string
enc.StringLenPrefixUint32(def.someStr2) // write 4 bytes (uint32) to sort length of string, then write length and convert to string
enc.Float32(def.val3) // write 4 bytes and convertt float and place into val3
enc.Bytes(def.someBytes) // write 4 bytes and place into someBytes
enc.Uint32(uint32(len(def.someSubStructs))) // write 4 bytes (uint32) to sort length of sub struct
for _, subStruct := range def.someSubStructs {
enc.Bool(subStruct.val1) // write 1 byte and convert to bool
enc.Float64(subStruct.val2) // write 8 bytes and convert to float64
enc.Float32(subStruct.val3.x) // write 4 bytes and convert to float32
enc.Float32(subStruct.val3.y) // write 4 bytes and convert to float32
enc.Float32(subStruct.val3.z) // write 4 bytes and convert to float32
}
if enc.Error() != nil {
return fmt.Errorf("encode: %w", enc.Error())
}
return nil
}