Skip to content

Commit 15fac42

Browse files
authored
Merge pull request #260 from benluddy/json-marshaler-trailing-data
Error on conversion to unstructured for invalid json.Marshalers.
2 parents 948bcfc + f07895d commit 15fac42

File tree

2 files changed

+191
-22
lines changed

2 files changed

+191
-22
lines changed

value/reflectcache.go

+34-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package value
1919
import (
2020
"bytes"
2121
"encoding/json"
22+
"errors"
2223
"fmt"
24+
"io"
2325
"reflect"
2426
"sort"
2527
"sync"
@@ -379,34 +381,47 @@ const maxDepth = 10000
379381
// unmarshal unmarshals the given data
380382
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
381383
func unmarshal(data []byte, v interface{}) error {
384+
// Build a decoder from the given data
385+
decoder := json.NewDecoder(bytes.NewBuffer(data))
386+
// Preserve numbers, rather than casting to float64 automatically
387+
decoder.UseNumber()
388+
// Run the decode
389+
if err := decoder.Decode(v); err != nil {
390+
return err
391+
}
392+
next := decoder.InputOffset()
393+
if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
394+
tail := bytes.TrimLeft(data[next:], " \t\r\n")
395+
return fmt.Errorf("unexpected trailing data at offset %d", len(data)-len(tail))
396+
}
397+
398+
// If the decode succeeds, post-process the object to convert json.Number objects to int64 or float64
382399
switch v := v.(type) {
383400
case *map[string]interface{}:
384-
// Build a decoder from the given data
385-
decoder := json.NewDecoder(bytes.NewBuffer(data))
386-
// Preserve numbers, rather than casting to float64 automatically
387-
decoder.UseNumber()
388-
// Run the decode
389-
if err := decoder.Decode(v); err != nil {
390-
return err
391-
}
392-
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
393401
return convertMapNumbers(*v, 0)
394402

395403
case *[]interface{}:
396-
// Build a decoder from the given data
397-
decoder := json.NewDecoder(bytes.NewBuffer(data))
398-
// Preserve numbers, rather than casting to float64 automatically
399-
decoder.UseNumber()
400-
// Run the decode
401-
if err := decoder.Decode(v); err != nil {
402-
return err
403-
}
404-
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
405404
return convertSliceNumbers(*v, 0)
406405

406+
case *interface{}:
407+
return convertInterfaceNumbers(v, 0)
408+
407409
default:
408-
return json.Unmarshal(data, v)
410+
return nil
411+
}
412+
}
413+
414+
func convertInterfaceNumbers(v *interface{}, depth int) error {
415+
var err error
416+
switch v2 := (*v).(type) {
417+
case json.Number:
418+
*v, err = convertNumber(v2)
419+
case map[string]interface{}:
420+
err = convertMapNumbers(v2, depth+1)
421+
case []interface{}:
422+
err = convertSliceNumbers(v2, depth+1)
409423
}
424+
return err
410425
}
411426

412427
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.

value/reflectcache_test.go

+157-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package value
1818

1919
import (
20+
"encoding/json"
21+
"fmt"
2022
"reflect"
2123
"testing"
2224
"time"
@@ -57,8 +59,9 @@ func (t Time) ToUnstructured() interface{} {
5759

5860
func TestToUnstructured(t *testing.T) {
5961
testcases := []struct {
60-
Data string
61-
Expected interface{}
62+
Data string
63+
Expected interface{}
64+
ExpectedErrorMessage string
6265
}{
6366
{Data: `null`, Expected: nil},
6467
{Data: `true`, Expected: true},
@@ -69,6 +72,12 @@ func TestToUnstructured(t *testing.T) {
6972
{Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}},
7073
{Data: `0`, Expected: int64(0)},
7174
{Data: `0.0`, Expected: float64(0)},
75+
{Data: "{} \t\r\n", Expected: map[string]interface{}{}},
76+
{Data: "{} \t\r\n}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
77+
{Data: "{} \t\r\n{}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
78+
{Data: "[] \t\r\n", Expected: []interface{}{}},
79+
{Data: "[] \t\r\n]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
80+
{Data: "[] \t\r\n[]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
7281
}
7382

7483
for _, tc := range testcases {
@@ -84,7 +93,13 @@ func TestToUnstructured(t *testing.T) {
8493
rv := reflect.ValueOf(custom)
8594
result, err := TypeReflectEntryOf(rv.Type()).ToUnstructured(rv)
8695
if err != nil {
87-
t.Fatal(err)
96+
if tc.ExpectedErrorMessage == "" {
97+
t.Fatal(err)
98+
} else if got := err.Error(); got != tc.ExpectedErrorMessage {
99+
t.Fatalf("expected error message %q but got %q", tc.ExpectedErrorMessage, got)
100+
}
101+
} else if tc.ExpectedErrorMessage != "" {
102+
t.Fatalf("expected error message %q but got nil error", tc.ExpectedErrorMessage)
88103
}
89104
if !reflect.DeepEqual(result, tc.Expected) {
90105
t.Errorf("expected %#v but got %#v", tc.Expected, result)
@@ -199,3 +214,142 @@ func TestTypeReflectEntryOf(t *testing.T) {
199214
})
200215
}
201216
}
217+
218+
func TestUnmarshal(t *testing.T) {
219+
for _, tc := range []struct {
220+
JSON string
221+
IntoType reflect.Type
222+
Want interface{}
223+
WantError bool
224+
}{
225+
{
226+
JSON: "{}}",
227+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
228+
Want: map[string]interface{}{},
229+
WantError: true,
230+
},
231+
{
232+
JSON: `1.0`,
233+
IntoType: reflect.TypeOf(json.Number("")),
234+
Want: json.Number("1.0"),
235+
},
236+
{
237+
JSON: `1`,
238+
IntoType: reflect.TypeOf(json.Number("")),
239+
Want: json.Number("1"),
240+
},
241+
{
242+
JSON: `1.0`,
243+
IntoType: reflect.TypeOf(float64(0)),
244+
Want: float64(1),
245+
},
246+
{
247+
JSON: `1`,
248+
IntoType: reflect.TypeOf(float64(0)),
249+
Want: float64(1),
250+
},
251+
{
252+
JSON: `1.0`,
253+
IntoType: reflect.TypeOf(int64(0)),
254+
Want: int64(0),
255+
WantError: true,
256+
},
257+
{
258+
JSON: `1`,
259+
IntoType: reflect.TypeOf(int64(0)),
260+
Want: int64(1),
261+
},
262+
{
263+
JSON: `1.0`,
264+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
265+
Want: float64(1),
266+
},
267+
{
268+
JSON: `1`,
269+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
270+
Want: int64(1),
271+
},
272+
{
273+
JSON: `[1.0,[1.0],{"":1.0}]`,
274+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
275+
Want: []interface{}{
276+
float64(1),
277+
[]interface{}{float64(1)},
278+
map[string]interface{}{"": float64(1)},
279+
},
280+
},
281+
{
282+
JSON: `[1.0,[1.0],{"":1.0}]`,
283+
IntoType: reflect.TypeOf([]interface{}{}),
284+
Want: []interface{}{
285+
float64(1),
286+
[]interface{}{float64(1)},
287+
map[string]interface{}{"": float64(1)},
288+
},
289+
},
290+
{
291+
JSON: `[1,[1],{"":1}]`,
292+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
293+
Want: []interface{}{
294+
int64(1),
295+
[]interface{}{int64(1)},
296+
map[string]interface{}{"": int64(1)},
297+
},
298+
},
299+
{
300+
JSON: `[1,[1],{"":1}]`,
301+
IntoType: reflect.TypeOf([]interface{}{}),
302+
Want: []interface{}{
303+
int64(1),
304+
[]interface{}{int64(1)},
305+
map[string]interface{}{"": int64(1)},
306+
},
307+
},
308+
{
309+
JSON: `{"x":1.0,"y":[1.0],"z":{"":1.0}}`,
310+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
311+
Want: map[string]interface{}{
312+
"x": float64(1),
313+
"y": []interface{}{float64(1)},
314+
"z": map[string]interface{}{"": float64(1)},
315+
},
316+
},
317+
{
318+
JSON: `{"x":1.0,"y":[1.0],"z":{"":1.0}}`,
319+
IntoType: reflect.TypeOf(map[string]interface{}{}),
320+
Want: map[string]interface{}{
321+
"x": float64(1),
322+
"y": []interface{}{float64(1)},
323+
"z": map[string]interface{}{"": float64(1)},
324+
},
325+
},
326+
{
327+
JSON: `{"x":1,"y":[1],"z":{"":1}}`,
328+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
329+
Want: map[string]interface{}{
330+
"x": int64(1),
331+
"y": []interface{}{int64(1)},
332+
"z": map[string]interface{}{"": int64(1)},
333+
},
334+
},
335+
{
336+
JSON: `{"x":1,"y":[1],"z":{"":1}}`,
337+
IntoType: reflect.TypeOf(map[string]interface{}{}),
338+
Want: map[string]interface{}{
339+
"x": int64(1),
340+
"y": []interface{}{int64(1)},
341+
"z": map[string]interface{}{"": int64(1)},
342+
},
343+
},
344+
} {
345+
t.Run(fmt.Sprintf("%s into %v", tc.JSON, reflect.PointerTo(tc.IntoType)), func(t *testing.T) {
346+
into := reflect.New(tc.IntoType)
347+
if err := unmarshal([]byte(tc.JSON), into.Interface()); tc.WantError != (err != nil) {
348+
t.Fatalf("unexpected error: %v", err)
349+
}
350+
if got := into.Elem().Interface(); !reflect.DeepEqual(tc.Want, got) {
351+
t.Errorf("want %#v (%T), got %#v (%T)", tc.Want, tc.Want, got, got)
352+
}
353+
})
354+
}
355+
}

0 commit comments

Comments
 (0)