Skip to content

Commit c689c68

Browse files
authored
unmarshall array: nested associative. Fixes #7 (#8)
This fixes a bug where a nested array might be associative.
1 parent 5f1a003 commit c689c68

File tree

3 files changed

+79
-36
lines changed

3 files changed

+79
-36
lines changed

consume.go

+52-2
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func consumeNext(data []byte, offset int) (interface{}, int, error) {
239239

240240
switch data[offset] {
241241
case 'a':
242-
return consumeArray(data, offset)
242+
return consumeIndexedOrAssociativeArray(data, offset)
243243
case 'b':
244244
return consumeBool(data, offset)
245245
case 'd':
@@ -258,7 +258,57 @@ func consumeNext(data []byte, offset int) (interface{}, int, error) {
258258
string(data[offset:]))
259259
}
260260

261-
func consumeArray(data []byte, offset int) ([]interface{}, int, error) {
261+
func consumeIndexedOrAssociativeArray(data []byte, offset int) (interface{}, int, error) {
262+
// Sometimes we don't know if the array is going to be indexed or
263+
// associative until we have already started to consume it.
264+
originalOffset := offset
265+
266+
// Try to consume it as an indexed array first.
267+
arr, offset, err := consumeIndexedArray(data, originalOffset)
268+
if err == nil {
269+
return arr, offset, err
270+
}
271+
272+
// Fallback to consuming an associative array
273+
return consumeAssociativeArray(data, originalOffset)
274+
}
275+
276+
func consumeAssociativeArray(data []byte, offset int) (map[interface{}]interface{}, int, error) {
277+
if !checkType(data, 'a', offset) {
278+
return map[interface{}]interface{}{}, -1, errors.New("not an array")
279+
}
280+
281+
// Skip over the "a:"
282+
offset += 2
283+
284+
rawLength, offset := consumeStringUntilByte(data, ':', offset)
285+
length, err := strconv.Atoi(rawLength)
286+
if err != nil {
287+
return map[interface{}]interface{}{}, -1, err
288+
}
289+
290+
// Skip over the ":{"
291+
offset += 2
292+
293+
result := map[interface{}]interface{}{}
294+
for i := 0; i < length; i++ {
295+
var key interface{}
296+
297+
key, offset, err = consumeNext(data, offset)
298+
if err != nil {
299+
return map[interface{}]interface{}{}, -1, err
300+
}
301+
302+
result[key], offset, err = consumeNext(data, offset)
303+
if err != nil {
304+
return map[interface{}]interface{}{}, -1, err
305+
}
306+
}
307+
308+
return result, offset, nil
309+
}
310+
311+
func consumeIndexedArray(data []byte, offset int) ([]interface{}, int, error) {
262312
if !checkType(data, 'a', offset) {
263313
return []interface{}{}, -1, errors.New("not an array")
264314
}

unserialize.go

+5-32
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ func checkType(data []byte, typeCharacter byte, offset int) bool {
7777
return len(data) > offset && data[offset] == typeCharacter
7878
}
7979

80-
func UnmarshalArray(data []byte) ([]interface{}, error) {
81-
v, _, err := consumeArray(data, 0)
80+
func UnmarshalIndexedArray(data []byte) ([]interface{}, error) {
81+
v, _, err := consumeIndexedArray(data, 0)
8282

8383
return v, err
8484
}
@@ -91,36 +91,9 @@ func UnmarshalAssociativeArray(data []byte) (map[interface{}]interface{}, error)
9191
return result, err
9292
}
9393

94-
if !checkType(data, 'a', 0) {
95-
return map[interface{}]interface{}{},
96-
errors.New("not an array or object")
97-
}
98-
99-
rawLength, offset := consumeStringUntilByte(data, ':', 2)
100-
length, err := strconv.Atoi(rawLength)
101-
if err != nil {
102-
return map[interface{}]interface{}{}, err
103-
}
104-
105-
// Skip over the ":{"
106-
offset += 2
107-
108-
result := map[interface{}]interface{}{}
109-
for i := 0; i < length; i++ {
110-
var key interface{}
111-
112-
key, offset, err = consumeNext(data, offset)
113-
if err != nil {
114-
return map[interface{}]interface{}{}, err
115-
}
116-
117-
result[key], offset, err = consumeNext(data, offset)
118-
if err != nil {
119-
return map[interface{}]interface{}{}, err
120-
}
121-
}
94+
result, _, err := consumeAssociativeArray(data, 0)
12295

123-
return result, nil
96+
return result, err
12497
}
12598

12699
func UnmarshalObject(data []byte, v interface{}) error {
@@ -187,7 +160,7 @@ func Unmarshal(data []byte, v interface{}) error {
187160
}
188161

189162
// Otherwise this must be a slice (array)
190-
v, err := UnmarshalArray(data)
163+
v, err := UnmarshalIndexedArray(data)
191164
if err != nil {
192165
return err
193166
}

unserialize_test.go

+22-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 or object": {
369+
"not an array": {
370370
[]byte("N;"),
371371
map[interface{}]interface{}{},
372-
errors.New("not an array or object"),
372+
errors.New("not an array"),
373373
},
374374
}
375375

@@ -503,3 +503,23 @@ func TestUnmarshalArrayThatContainsObject(t *testing.T) {
503503
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
504504
}
505505
}
506+
507+
// https://github.com/elliotchance/phpserialize/issues/7
508+
func TestUnmarshalArrayThatContainsInteger(t *testing.T) {
509+
data := `a:3:{s:4:"name";s:2:"tw";s:3:"age";i:123;s:4:"wife";a:1:{s:1:"x";s:1:"y";}}`
510+
var result map[interface{}]interface{}
511+
err := phpserialize.Unmarshal([]byte(data), &result)
512+
expectErrorToNotHaveOccurred(t, err)
513+
514+
expected := map[interface{}]interface{}{
515+
"wife": map[interface{}]interface{}{
516+
"x": "y",
517+
},
518+
"name": "tw",
519+
"age": int64(123),
520+
}
521+
522+
if !reflect.DeepEqual(result, expected) {
523+
t.Errorf("Expected:\n %#+v\nGot:\n %#+v", expected, result)
524+
}
525+
}

0 commit comments

Comments
 (0)