Skip to content

Commit 0fa3b04

Browse files
authored
Merge pull request #3 from elliotchance/unserialise-into-map
TestUnmarshalObjectIntoMap
2 parents 00fbca9 + f0aaf95 commit 0fa3b04

File tree

5 files changed

+298
-84
lines changed

5 files changed

+298
-84
lines changed

consume.go

+115-43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"reflect"
66
"strconv"
7+
"fmt"
78
)
89

910
// The internal consume functions work as the parser/lexer when reading
@@ -90,7 +91,7 @@ func consumeStringRealPart(data []byte, offset int) (string, int, error) {
9091
s := DecodePHPString(data)
9192

9293
// The +2 is to skip over the final '";'
93-
return s[offset : offset+length], offset + length + 2, nil
94+
return s[offset: offset+length], offset + length + 2, nil
9495
}
9596

9697
func consumeNil(data []byte, offset int) (interface{}, int, error) {
@@ -109,24 +110,23 @@ func consumeBool(data []byte, offset int) (bool, int, error) {
109110
return data[offset+2] == '1', offset + 4, nil
110111
}
111112

112-
func consumeObject(data []byte, offset int, v interface{}) (int, error) {
113-
if !checkType(data, 'O', offset) {
114-
return -1, errors.New("not an object")
115-
}
113+
func consumeObjectAsMap(data []byte, offset int) (
114+
map[interface{}]interface{}, int, error) {
115+
result := map[interface{}]interface{}{}
116116

117117
// Read the class name. The class name follows the same format as a
118118
// string. We could just ignore the length and hope that no class name
119119
// ever had a non-ascii characters in it, but this is safer - and
120120
// probably easier.
121121
_, offset, err := consumeStringRealPart(data, offset+2)
122122
if err != nil {
123-
return -1, err
123+
return nil, -1, err
124124
}
125125

126126
// Read the number of elements in the object.
127127
length, offset, err := consumeIntPart(data, offset)
128128
if err != nil {
129-
return -1, err
129+
return nil, -1, err
130130
}
131131

132132
// Skip over the '{'
@@ -141,69 +141,95 @@ func consumeObject(data []byte, offset int, v interface{}) (int, error) {
141141
// about this.
142142
key, offset, err = consumeString(data, offset)
143143
if err != nil {
144-
return -1, err
144+
return nil, -1, err
145145
}
146146

147-
// Check the the key exists in the struct, otherwise the value
148-
// is discarded.
149-
//
150-
// We need to uppercase the first letter for compatibility.
151-
// The Marshal() function does the opposite of this.
152-
field := reflect.ValueOf(v).Elem().
153-
FieldByName(upperCaseFirstLetter(key))
154-
155147
// If the next item is an object we can't simply consume it,
156148
// rather we send the reflect.Value back through consumeObject
157149
// so the recursion can be handled correctly.
158150
if data[offset] == 'O' {
159-
var subV interface{}
160-
161-
if field.IsValid() {
162-
subV = field.Addr().Interface()
163-
} else {
164-
// If the field (key) does not exist on the
165-
// struct we pass through a dummy object that no
166-
// keys so that all of the values are discarded
167-
// but the parser can continue to operate
168-
// easily.
169-
subV = &dummyObject{}
170-
}
151+
var subMap interface{}
171152

172-
offset, err = consumeObject(data, offset, subV)
153+
subMap, offset, err = consumeObjectAsMap(data, offset)
173154
if err != nil {
174-
return -1, err
155+
return nil, -1, err
175156
}
157+
158+
result[key] = subMap
176159
} else {
177160
value, offset, err = consumeNext(data, offset)
178161
if err != nil {
179-
return -1, err
162+
return nil, -1, err
180163
}
181164

182-
if field.IsValid() {
183-
setField(field, reflect.ValueOf(value))
184-
}
165+
result[key] = value
185166
}
186167
}
187168

188169
// The +1 is for the final '}'
189-
return offset + 1, nil
170+
return result, offset + 1, nil
190171
}
191172

192-
func setField(field, value reflect.Value) {
193-
switch field.Type().Kind() {
194-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
195-
reflect.Int64:
196-
field.SetInt(value.Int())
173+
func setField(obj interface{}, name string, value interface{}) error {
174+
structValue := reflect.ValueOf(obj).Elem()
175+
176+
// We need to uppercase the first letter for compatibility.
177+
// The Marshal() function does the opposite of this.
178+
structFieldValue := structValue.FieldByName(upperCaseFirstLetter(name))
179+
180+
if !structFieldValue.IsValid() {
181+
return fmt.Errorf("no such field: %s in obj", name)
182+
}
183+
184+
if !structFieldValue.CanSet() {
185+
return fmt.Errorf("cannot set %s field value", name)
186+
}
187+
188+
val := reflect.ValueOf(value)
189+
switch structFieldValue.Type().Kind() {
190+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
191+
structFieldValue.SetInt(val.Int())
197192

198193
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
199-
field.SetUint(value.Uint())
194+
structFieldValue.SetUint(val.Uint())
200195

201196
case reflect.Float32, reflect.Float64:
202-
field.SetFloat(value.Float())
197+
structFieldValue.SetFloat(val.Float())
198+
199+
case reflect.Struct:
200+
m := val.Interface().(map[interface{}]interface{})
201+
fillStruct(structFieldValue.Addr().Interface(), m)
203202

204203
default:
205-
field.Set(value)
204+
structFieldValue.Set(val)
205+
}
206+
207+
return nil
208+
}
209+
210+
// https://stackoverflow.com/questions/26744873/converting-map-to-struct
211+
func fillStruct(obj interface{}, m map[interface{}]interface{}) error {
212+
for k, v := range m {
213+
err := setField(obj, k.(string), v)
214+
if err != nil {
215+
return err
216+
}
217+
}
218+
219+
return nil
220+
}
221+
222+
func consumeObject(data []byte, offset int, v interface{}) (int, error) {
223+
if !checkType(data, 'O', offset) {
224+
return -1, errors.New("not an object")
225+
}
226+
227+
m, offset, err := consumeObjectAsMap(data, offset)
228+
if err != nil {
229+
return -1, err
206230
}
231+
232+
return offset, fillStruct(v, m)
207233
}
208234

209235
func consumeNext(data []byte, offset int) (interface{}, int, error) {
@@ -212,6 +238,8 @@ func consumeNext(data []byte, offset int) (interface{}, int, error) {
212238
}
213239

214240
switch data[offset] {
241+
case 'a':
242+
return consumeArray(data, offset)
215243
case 'b':
216244
return consumeBool(data, offset)
217245
case 'd':
@@ -222,8 +250,52 @@ func consumeNext(data []byte, offset int) (interface{}, int, error) {
222250
return consumeString(data, offset)
223251
case 'N':
224252
return consumeNil(data, offset)
253+
case 'O':
254+
return consumeObjectAsMap(data, offset)
225255
}
226256

227257
return nil, -1, errors.New("can not consume type: " +
228258
string(data[offset:]))
229259
}
260+
261+
func consumeArray(data []byte, offset int) ([]interface{}, int, error) {
262+
if !checkType(data, 'a', offset) {
263+
return []interface{}{}, -1, errors.New("not an array")
264+
}
265+
266+
rawLength, offset := consumeStringUntilByte(data, ':', offset+2)
267+
length, err := strconv.Atoi(rawLength)
268+
if err != nil {
269+
return []interface{}{}, -1, err
270+
}
271+
272+
// Skip over the ":{"
273+
offset += 2
274+
275+
result := make([]interface{}, length)
276+
for i := 0; i < length; i++ {
277+
// Even non-associative arrays (arrays that are zero-indexed)
278+
// still have their keys serialized. We need to read these
279+
// indexes to make sure we are actually decoding a slice and not
280+
// a map.
281+
var index int64
282+
index, offset, err = consumeInt(data, offset)
283+
if err != nil {
284+
return []interface{}{}, -1, err
285+
}
286+
287+
if index != int64(i) {
288+
return []interface{}{}, -1,
289+
errors.New("cannot decode map as slice")
290+
}
291+
292+
// Now we consume the value
293+
result[i], offset, err = consumeNext(data, offset)
294+
if err != nil {
295+
return []interface{}{}, -1, err
296+
}
297+
}
298+
299+
// The +1 is for the final '}'
300+
return result, offset+1, nil
301+
}

unserialize.go

+10-39
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"strings"
99
)
1010

11-
type dummyObject struct{ Qux float64 }
12-
1311
// findByte will return the first position at or after offset of the specified
1412
// byte. -1 is returned if the byte is not found.
1513
func findByte(data []byte, lookingFor byte, offset int) int {
@@ -80,49 +78,22 @@ func checkType(data []byte, typeCharacter byte, offset int) bool {
8078
}
8179

8280
func UnmarshalArray(data []byte) ([]interface{}, error) {
83-
if !checkType(data, 'a', 0) {
84-
return []interface{}{}, errors.New("not an array")
85-
}
86-
87-
rawLength, offset := consumeStringUntilByte(data, ':', 2)
88-
length, err := strconv.Atoi(rawLength)
89-
if err != nil {
90-
return []interface{}{}, err
91-
}
92-
93-
// Skip over the ":{"
94-
offset += 2
81+
v, _, err := consumeArray(data, 0)
9582

96-
result := make([]interface{}, length)
97-
for i := 0; i < length; i++ {
98-
// Even non-associative arrays (arrays that are zero-indexed)
99-
// still have their keys serialized. We need to read these
100-
// indexes to make sure we are actually decoding a slice and not
101-
// a map.
102-
var index int64
103-
index, offset, err = consumeInt(data, offset)
104-
if err != nil {
105-
return []interface{}{}, err
106-
}
83+
return v, err
84+
}
10785

108-
if index != int64(i) {
109-
return []interface{}{},
110-
errors.New("cannot decode map as slice")
111-
}
86+
func UnmarshalAssociativeArray(data []byte) (map[interface{}]interface{}, error) {
87+
// We may be unmarshalling an object into a map.
88+
if checkType(data, 'O', 0) {
89+
result, _, err := consumeObjectAsMap(data, 0)
11290

113-
// Now we consume the value
114-
result[i], offset, err = consumeNext(data, offset)
115-
if err != nil {
116-
return []interface{}{}, err
117-
}
91+
return result, err
11892
}
11993

120-
return result, nil
121-
}
122-
123-
func UnmarshalAssociativeArray(data []byte) (map[interface{}]interface{}, error) {
12494
if !checkType(data, 'a', 0) {
125-
return map[interface{}]interface{}{}, errors.New("not an array")
95+
return map[interface{}]interface{}{},
96+
errors.New("not an array or object")
12697
}
12798

12899
rawLength, offset := consumeStringUntilByte(data, ':', 2)

unserialize_test.go

+58-2
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,10 @@ func TestUnmarshalAssociativeArray(t *testing.T) {
366366
map[interface{}]interface{}{int64(1): int64(10), int64(2): "foo"},
367367
nil,
368368
},
369-
"not an array": {
369+
"not an array or object": {
370370
[]byte("N;"),
371371
map[interface{}]interface{}{},
372-
errors.New("not an array"),
372+
errors.New("not an array or object"),
373373
},
374374
}
375375

@@ -447,3 +447,59 @@ func TestUnmarshalObject(t *testing.T) {
447447
t.Errorf("Expected %v, got %v", "yay", result.Baz)
448448
}
449449
}
450+
451+
func TestUnmarshalObjectIntoMap(t *testing.T) {
452+
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\";}"
453+
var result map[interface{}]interface{}
454+
err := phpserialize.Unmarshal([]byte(data), &result)
455+
expectErrorToNotHaveOccurred(t, err)
456+
457+
expected := map[interface{}]interface{}{
458+
"baz": "yay",
459+
"foo": int64(10),
460+
"bar": map[interface{}]interface{}{
461+
"qux": 1.23,
462+
},
463+
}
464+
465+
if !reflect.DeepEqual(result, expected) {
466+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
467+
}
468+
}
469+
470+
func TestUnmarshalObjectIntoMapContainingArray(t *testing.T) {
471+
data := "O:7:\"struct1\":3:{s:3:\"foo\";i:10;s:3:\"bar\";a:3:{i:0;i:7;i:1;i:8;i:2;i:9;}s:3:\"baz\";s:3:\"yay\";}"
472+
var result map[interface{}]interface{}
473+
err := phpserialize.Unmarshal([]byte(data), &result)
474+
expectErrorToNotHaveOccurred(t, err)
475+
476+
expected := map[interface{}]interface{}{
477+
"baz": "yay",
478+
"foo": int64(10),
479+
"bar": []interface{}{int64(7), int64(8), int64(9)},
480+
}
481+
482+
if !reflect.DeepEqual(result, expected) {
483+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
484+
}
485+
}
486+
487+
func TestUnmarshalArrayThatContainsObject(t *testing.T) {
488+
data := "a:3:{i:0;O:7:\"struct1\":2:{s:3:\"foo\";i:10;s:3:\"baz\";s:3:\"yay\";}i:1;i:8;i:2;i:9;}"
489+
var result []interface{}
490+
err := phpserialize.Unmarshal([]byte(data), &result)
491+
expectErrorToNotHaveOccurred(t, err)
492+
493+
expected := []interface{}{
494+
map[interface{}]interface{}{
495+
"baz": "yay",
496+
"foo": int64(10),
497+
},
498+
int64(8),
499+
int64(9),
500+
}
501+
502+
if !reflect.DeepEqual(result, expected) {
503+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
504+
}
505+
}

0 commit comments

Comments
 (0)