Skip to content

Commit 0419463

Browse files
committed
Add ability to serialize Nil pointers
All pointers have a nil/not nil prefix, it tries to be backwards compatible by ignoring if such prefix is missing. It is also a 5 byte payload to avoid of serialized messages to have those 5 bytes by change
1 parent 50f131e commit 0419463

File tree

3 files changed

+126
-18
lines changed

3 files changed

+126
-18
lines changed

codec/reflectcodec/type_codec.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import (
1919
// DefaultTagName that enables serialization.
2020
const DefaultTagName = "serialize"
2121

22+
var (
23+
MagicBytesPtrNil = []byte{0xff, 0x32, 0x31, 0x36, 0x98}
24+
MagicBytesPtr = []byte{0xc3, 0xa1, 0x00, 0x33, 0x25}
25+
magicBytesPtrPrefixSize = len(MagicBytesPtrNil)
26+
)
27+
2228
var (
2329
_ codec.Codec = (*genericCodec)(nil)
2430

@@ -85,7 +91,13 @@ func (c *genericCodec) Size(value interface{}) (int, error) {
8591
return 0, errMarshalNil // can't marshal nil
8692
}
8793

88-
size, _, err := c.size(reflect.ValueOf(value))
94+
metaValue := reflect.ValueOf(value)
95+
switch valueKind := metaValue.Kind(); valueKind {
96+
case reflect.Ptr:
97+
metaValue = metaValue.Elem()
98+
}
99+
100+
size, _, err := c.size(metaValue)
89101
return size, err
90102
}
91103

@@ -114,10 +126,13 @@ func (c *genericCodec) size(value reflect.Value) (int, bool, error) {
114126
return wrappers.StringLen(value.String()), false, nil
115127
case reflect.Ptr:
116128
if value.IsNil() {
117-
// Can't marshal nil pointers (but nil slices are fine)
118-
return 0, false, errMarshalNil
129+
return magicBytesPtrPrefixSize, false, nil
119130
}
120-
return c.size(value.Elem())
131+
size, _, err := c.size(value.Elem())
132+
if err != nil {
133+
return 0, false, err
134+
}
135+
return magicBytesPtrPrefixSize + size, false, nil
121136

122137
case reflect.Interface:
123138
if value.IsNil() {
@@ -279,7 +294,13 @@ func (c *genericCodec) MarshalInto(value interface{}, p *wrappers.Packer) error
279294
return errMarshalNil // can't marshal nil
280295
}
281296

282-
return c.marshal(reflect.ValueOf(value), p, c.maxSliceLen)
297+
metaValue := reflect.ValueOf(value)
298+
switch valueKind := metaValue.Kind(); valueKind {
299+
case reflect.Ptr:
300+
metaValue = metaValue.Elem()
301+
}
302+
303+
return c.marshal(metaValue, p, c.maxSliceLen)
283304
}
284305

285306
// marshal writes the byte representation of [value] to [p]
@@ -318,8 +339,13 @@ func (c *genericCodec) marshal(value reflect.Value, p *wrappers.Packer, maxSlice
318339
p.PackBool(value.Bool())
319340
return p.Err
320341
case reflect.Ptr:
321-
if value.IsNil() { // Can't marshal nil (except nil slices)
322-
return errMarshalNil
342+
if value.IsNil() {
343+
p.PackFixedBytes(MagicBytesPtrNil)
344+
return p.Err
345+
}
346+
p.PackFixedBytes(MagicBytesPtr)
347+
if p.Err != nil {
348+
return p.Err
323349
}
324350
return c.marshal(value.Elem(), p, c.maxSliceLen)
325351
case reflect.Interface:
@@ -643,6 +669,10 @@ func (c *genericCodec) unmarshal(p *wrappers.Packer, value reflect.Value, maxSli
643669
// Create a new pointer to a new value of the underlying type
644670
v := reflect.New(t)
645671
// Fill the value
672+
if bytes.Equal(MagicBytesPtrNil, p.UnpackIfMatches(MagicBytesPtrNil, MagicBytesPtr)) {
673+
// Nil
674+
return nil
675+
}
646676
if err := c.unmarshal(p, v.Elem(), c.maxSliceLen); err != nil {
647677
return fmt.Errorf("couldn't unmarshal pointer: %w", err)
648678
}

codec/test_codec.go

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ var (
2323
TestBigArray,
2424
TestPointerToStruct,
2525
TestSliceOfStruct,
26+
TestStructWithPtr,
27+
TestStructWithPtrNil,
2628
TestInterface,
2729
TestSliceOfInterface,
2830
TestArrayOfInterface,
@@ -63,7 +65,8 @@ type Foo interface {
6365
}
6466

6567
type MyInnerStruct struct {
66-
Str string `serialize:"true"`
68+
Str string `serialize:"true"`
69+
NumberNotProvided *int32 `serialize:"true":121`
6770
}
6871

6972
func (*MyInnerStruct) Foo() int {
@@ -86,6 +89,11 @@ type MyInnerStruct3 struct {
8689
F Foo `serialize:"true"`
8790
}
8891

92+
type MyStructWithPtr struct {
93+
N1 *int32 `serialize:"true"`
94+
N2 *int64 `serialize:"true"`
95+
}
96+
8997
type myStruct struct {
9098
InnerStruct MyInnerStruct `serialize:"true"`
9199
InnerStruct2 *MyInnerStruct `serialize:"true"`
@@ -145,21 +153,23 @@ func TestStruct(codec GeneralCodec, t testing.TB) {
145153
myMap7["key"] = "value"
146154
myMap7[int32(1)] = int32(2)
147155

156+
number := int32(8)
157+
148158
myStructInstance := myStruct{
149-
InnerStruct: MyInnerStruct{"hello"},
150-
InnerStruct2: &MyInnerStruct{"yello"},
159+
InnerStruct: MyInnerStruct{"hello", nil},
160+
InnerStruct2: &MyInnerStruct{"yello", nil},
151161
Member1: 1,
152162
Member2: 2,
153163
MySlice: []byte{1, 2, 3, 4},
154164
MySlice2: []string{"one", "two", "three"},
155-
MySlice3: []MyInnerStruct{{"abc"}, {"ab"}, {"c"}},
165+
MySlice3: []MyInnerStruct{{"abc", nil}, {"ab", &number}, {"c", nil}},
156166
MySlice4: []*MyInnerStruct2{{true}, {}},
157167
MySlice5: []Foo{&MyInnerStruct2{true}, &MyInnerStruct2{}},
158168
MyArray: [4]byte{5, 6, 7, 8},
159169
MyArray2: [5]string{"four", "five", "six", "seven"},
160-
MyArray3: [3]MyInnerStruct{{"d"}, {"e"}, {"f"}},
170+
MyArray3: [3]MyInnerStruct{{"d", nil}, {"e", nil}, {"f", nil}},
161171
MyArray4: [2]*MyInnerStruct2{{}, {true}},
162-
MyInterface: &MyInnerStruct{"yeet"},
172+
MyInterface: &MyInnerStruct{"yeet", &number},
163173
InnerStruct3: MyInnerStruct3{
164174
Str: "str",
165175
M1: MyInnerStruct{
@@ -414,20 +424,70 @@ func TestPointerToStruct(codec GeneralCodec, t testing.TB) {
414424
require.Equal(myPtr, myPtrUnmarshaled)
415425
}
416426

427+
func TestStructWithPtr(codec GeneralCodec, t testing.TB) {
428+
require := require.New(t)
429+
n1 := int32(5)
430+
n2 := int64(10)
431+
struct1 := MyStructWithPtr{
432+
N1: &n1,
433+
N2: &n2,
434+
}
435+
436+
require.NoError(codec.RegisterType(&MyStructWithPtr{}))
437+
manager := NewDefaultManager()
438+
require.NoError(manager.RegisterCodec(0, codec))
439+
440+
bytes, err := manager.Marshal(0, struct1)
441+
require.NoError(err)
442+
443+
bytesLen, err := manager.Size(0, struct1)
444+
require.NoError(err)
445+
require.Len(bytes, bytesLen)
446+
447+
var struct1Unmarshaled MyStructWithPtr
448+
version, err := manager.Unmarshal(bytes, &struct1Unmarshaled)
449+
require.NoError(err)
450+
require.Zero(version)
451+
require.Equal(struct1, struct1Unmarshaled)
452+
}
453+
454+
func TestStructWithPtrNil(codec GeneralCodec, t testing.TB) {
455+
require := require.New(t)
456+
struct1 := MyStructWithPtr{}
457+
458+
require.NoError(codec.RegisterType(&MyStructWithPtr{}))
459+
manager := NewDefaultManager()
460+
require.NoError(manager.RegisterCodec(0, codec))
461+
462+
bytes, err := manager.Marshal(0, struct1)
463+
require.NoError(err)
464+
465+
bytesLen, err := manager.Size(0, struct1)
466+
require.NoError(err)
467+
require.Len(bytes, bytesLen)
468+
469+
var struct1Unmarshaled MyStructWithPtr
470+
version, err := manager.Unmarshal(bytes, &struct1Unmarshaled)
471+
require.NoError(err)
472+
require.Zero(version)
473+
require.Equal(struct1, struct1Unmarshaled)
474+
}
475+
417476
// Test marshalling a slice of structs
418477
func TestSliceOfStruct(codec GeneralCodec, t testing.TB) {
419478
require := require.New(t)
420-
479+
n1 := int32(-1)
480+
n2 := int32(0xff)
421481
mySlice := []MyInnerStruct3{
422482
{
423483
Str: "One",
424-
M1: MyInnerStruct{"Two"},
425-
F: &MyInnerStruct{"Three"},
484+
M1: MyInnerStruct{"Two", &n1},
485+
F: &MyInnerStruct{"Three", &n2},
426486
},
427487
{
428488
Str: "Four",
429-
M1: MyInnerStruct{"Five"},
430-
F: &MyInnerStruct{"Six"},
489+
M1: MyInnerStruct{"Five", nil},
490+
F: &MyInnerStruct{"Six", nil},
431491
},
432492
}
433493
require.NoError(codec.RegisterType(&MyInnerStruct{}))

utils/wrappers/packing.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package wrappers
55

66
import (
7+
"bytes"
78
"encoding/binary"
89
"errors"
910
"math"
@@ -179,6 +180,23 @@ func (p *Packer) PackFixedBytes(bytes []byte) {
179180
p.Offset += len(bytes)
180181
}
181182

183+
func (p *Packer) UnpackIfMatches(values ...[]byte) []byte {
184+
totalLength := len(p.Bytes)
185+
for _, value := range values {
186+
length := len(value)
187+
if totalLength-p.Offset < length {
188+
continue
189+
}
190+
191+
if bytes.Equal(value, p.Bytes[p.Offset:p.Offset+length]) {
192+
p.Offset += length
193+
return value
194+
}
195+
}
196+
197+
return nil
198+
}
199+
182200
// UnpackFixedBytes unpack a byte slice, with no length descriptor from the byte
183201
// array
184202
func (p *Packer) UnpackFixedBytes(size int) []byte {

0 commit comments

Comments
 (0)