Skip to content

Commit 9905247

Browse files
danielbairstow97danielbairstow97
and
danielbairstow97
authored
Added support of unmarshalling and marshalling of pointers (#32)
During development of my own project I found some issues when trying to marshal php Nulls `N` into any Go struct fields. Was seeing uncaught panics such as `reflect: call of reflect.Value.Set on zero Value` if the value trying to be set on a struct field was Null. Additionally if a value was being set on a struct field of type reflect.Ptr that was nil a panic would occur when calling the .Set method on the struct field e.g. `reflect.Set: value of type string is not assignable to type *string` I've added testing to showcase the now supported functionality: * When serializing if a pointer value is Nil the MarshalNil function will be called * When unserializing if the value passed in is a nil interface the struct field will be left unset (set as default value) rather then panic * If unmarshalling to a pointer struct field, the field will first be instantiated before attempting to set the value of the pointer I used this Go playground to try base behaviour off of the `encoding/json` package: https://go.dev/play/p/5td0XqinlIP Co-authored-by: danielbairstow97 <[email protected]>
1 parent c610362 commit 9905247

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed

consume.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ func setField(structFieldValue reflect.Value, value interface{}) error {
175175
}
176176

177177
val := reflect.ValueOf(value)
178+
if !val.IsValid() {
179+
// structFieldValue will be set to default.
180+
return nil
181+
}
182+
178183
switch structFieldValue.Type().Kind() {
179184
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
180185
structFieldValue.SetInt(val.Int())
@@ -211,7 +216,10 @@ func setField(structFieldValue reflect.Value, value interface{}) error {
211216
}
212217

213218
structFieldValue.Set(arrayOfObjects)
214-
219+
case reflect.Ptr:
220+
// Instantiate structFieldValue.
221+
structFieldValue.Set(reflect.New(structFieldValue.Type().Elem()))
222+
return setField(structFieldValue.Elem(), value)
215223
default:
216224
structFieldValue.Set(val)
217225
}

serialize.go

+3
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ func Marshal(input interface{}, options *MarshalOptions) ([]byte, error) {
247247
return MarshalStruct(input, options)
248248

249249
case reflect.Ptr:
250+
if value.IsNil() {
251+
return MarshalNil(), nil
252+
}
250253
return Marshal(value.Elem().Interface(), options)
251254

252255
default:

serialize_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import (
66
"testing"
77
)
88

9+
var (
10+
heyStr = "hey"
11+
)
12+
913
type struct1 struct {
1014
Foo int
1115
Bar Struct2
@@ -33,6 +37,13 @@ type Struct3 struct {
3337
StringArray []string
3438
}
3539

40+
type Nillable struct {
41+
Foo string
42+
Bar Struct2
43+
FooPtr *string
44+
BarPtr *Struct2
45+
}
46+
3647
type marshalTest struct {
3748
input interface{}
3849
output []byte
@@ -167,6 +178,18 @@ var marshalTests = map[string]marshalTest{
167178
[]byte("O:8:\"stdClass\":3:{s:3:\"foo\";i:20;s:3:\"bar\";O:8:\"stdClass\":1:{s:3:\"qux\";d:7.89;}s:3:\"baz\";s:3:\"yay\";}"),
168179
getStdClassOnly(),
169180
},
181+
182+
// encode object with pointers
183+
"Nillable{Foo string, Bar Struct2{Qux float64}, FooPtr *string, BarPtr *Struct2{Qux float64}": {
184+
Nillable{"yay", Struct2{10}, &heyStr, &Struct2{}},
185+
[]byte("O:8:\"Nillable\":4:{s:3:\"foo\";s:3:\"yay\";s:3:\"bar\";O:7:\"Struct2\":1:{s:3:\"qux\";d:10;}s:6:\"fooPtr\";s:3:\"hey\";s:6:\"barPtr\";O:7:\"Struct2\":1:{s:3:\"qux\";d:0;}}"),
186+
nil,
187+
},
188+
"Nillable{Foo string, Bar Struct2{Qux float64}, FooPtr <nil>, BarPtr <nil>": {
189+
Nillable{"", Struct2{}, nil, nil},
190+
[]byte("O:8:\"Nillable\":4:{s:3:\"foo\";s:0:\"\";s:3:\"bar\";O:7:\"Struct2\":1:{s:3:\"qux\";d:0;}s:6:\"fooPtr\";N;s:6:\"barPtr\";N;}"),
191+
nil,
192+
},
170193
}
171194

172195
func TestMarshal(t *testing.T) {

unserialize_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,74 @@ func TestUnmarshalObjectWithTags(t *testing.T) {
537537
}
538538
}
539539

540+
func TestUnmarshalPointers(t *testing.T) {
541+
data := "O:8:\"Nillable\":4:{s:3:\"foo\";s:3:\"yay\";s:3:\"bar\";O:7:\"Struct2\":1:{s:3:\"qux\";d:10;}s:6:\"fooPtr\";s:3:\"hey\";s:6:\"barPtr\";O:7:\"Struct2\":1:{s:3:\"qux\";d:0;}}"
542+
target := &Nillable{
543+
Foo: "yay",
544+
Bar: Struct2{
545+
Qux: 10,
546+
},
547+
FooPtr: &heyStr,
548+
BarPtr: &Struct2{
549+
Qux: 0,
550+
},
551+
}
552+
553+
var result Nillable
554+
err := phpserialize.Unmarshal([]byte(data), &result)
555+
expectErrorToNotHaveOccurred(t, err)
556+
557+
if result.Foo != target.Foo {
558+
t.Errorf("Expected %v, got %v for Foo", target.Foo, result.Foo)
559+
}
560+
561+
if result.Bar.Qux != target.Bar.Qux {
562+
t.Errorf("Expected %v, got %v for Bar", target.Bar, result.Bar)
563+
}
564+
565+
if result.FooPtr == nil || *result.FooPtr != *target.FooPtr {
566+
t.Errorf("Expected %v, got %v for FooPtr", *target.FooPtr, *result.FooPtr)
567+
}
568+
569+
if result.BarPtr == nil || result.BarPtr.Qux != result.BarPtr.Qux {
570+
t.Errorf("Expected %v, got %v for BarPtr", target.BarPtr, result.BarPtr)
571+
}
572+
573+
}
574+
575+
func TestUnmarshalPointersWithNull(t *testing.T) {
576+
data := "O:8:\"Nillable\":4:{s:3:\"foo\";s:0:\"\";s:3:\"bar\";O:7:\"Struct2\":1:{s:3:\"qux\";d:0;}s:6:\"fooPtr\";N;s:6:\"barPtr\";N;}"
577+
578+
target := &Nillable{
579+
Foo: "",
580+
Bar: Struct2{
581+
Qux: 0,
582+
},
583+
FooPtr: nil,
584+
BarPtr: nil,
585+
}
586+
587+
var result Nillable
588+
err := phpserialize.Unmarshal([]byte(data), &result)
589+
expectErrorToNotHaveOccurred(t, err)
590+
591+
if result.Foo != target.Foo {
592+
t.Errorf("Expected %v, got %v for Foo", target.Foo, result.Foo)
593+
}
594+
595+
if result.Bar.Qux != target.Bar.Qux {
596+
t.Errorf("Expected %v, got %v for Bar", target.Bar, result.Bar)
597+
}
598+
599+
if result.FooPtr != target.FooPtr {
600+
t.Errorf("Expected %v, got %v for FooPtr", target.FooPtr, result.FooPtr)
601+
}
602+
603+
if result.BarPtr != target.BarPtr {
604+
t.Errorf("Expected %v, got %v for BarPtr", target.BarPtr, result.BarPtr)
605+
}
606+
}
607+
540608
func TestUnmarshalObjectIntoMap(t *testing.T) {
541609
data := "O:7:\"struct1\":3:{s:3:\"foo\";i:10;s:3:\"bar\";O:7:\"Struct2\":1:{s:3:\"qux\";d:1.23;}s:3:\"baz\";s:3:\"yay\";}"
542610
var result map[interface{}]interface{}

0 commit comments

Comments
 (0)