Skip to content

Commit a75a883

Browse files
committed
fix: clarify behavior of empty vs nil slices
1 parent 3002a3d commit a75a883

File tree

4 files changed

+130
-41
lines changed

4 files changed

+130
-41
lines changed

marshal.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,19 @@ func marshal(ctx context.Context, in reflect.Value) (r interface{}, err error) {
8888
return
8989
}
9090

91-
func marshalList(ctx context.Context, in reflect.Value) ([]interface{}, error) {
92-
var out []interface{}
91+
func marshalList(ctx context.Context, in reflect.Value) (out []interface{}, err error) {
92+
if in.Type().Kind() == reflect.Slice && in.IsNil() {
93+
return out, nil
94+
}
9395

96+
out = make([]interface{}, in.Len())
9497
for i := 0; i < in.Len(); i++ {
9598
ctx := ctx.WithIndex(i, in.Type())
9699
r, err := marshal(ctx, in.Index(i))
97100
if err != nil {
98101
return nil, err
99102
}
100-
out = append(out, r)
103+
out[i] = r
101104
}
102105

103106
return out, nil

marshal_test.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,6 @@ var _ = Describe("Marshal", func() {
141141
expectToMarshal(struct{ P, p string }{P: "foo", p: "bar"}, `{"P":"foo"}`)
142142
})
143143

144-
It("marshals a slice", func() {
145-
s := []interface{}{"hello", true, 42}
146-
expectToMarshal(struct{ S []interface{} }{S: s}, `{"S":["hello",true,42]}`)
147-
expectToMarshal(struct {
148-
S *[]interface{}
149-
}{S: &s}, `{"S":["hello",true,42]}`)
150-
})
151-
152144
It("marshals an array", func() {
153145
s := [3]interface{}{"hello", true, 42}
154146
expectToMarshal(struct{ S [3]interface{} }{S: s}, `{"S":["hello",true,42]}`)
@@ -157,8 +149,34 @@ var _ = Describe("Marshal", func() {
157149
}{S: &s}, `{"S":["hello",true,42]}`)
158150
})
159151

152+
Context("slices", func() {
153+
It("marshals slices with interface{} values", func() {
154+
s := []interface{}{"hello", true, 42}
155+
expectToMarshal(struct{ S []interface{} }{S: s}, `{"S":["hello",true,42]}`)
156+
expectToMarshal(struct{ S *[]interface{} }{S: &s}, `{"S":["hello",true,42]}`)
157+
})
158+
159+
It("marshals slices with string values", func() {
160+
s := []string{"hello", "true", "42"}
161+
expectToMarshal(struct{ S []string }{S: s}, `{"S":["hello","true","42"]}`)
162+
expectToMarshal(struct{ S *[]string }{S: &s}, `{"S":["hello","true","42"]}`)
163+
})
164+
165+
It("marshals empty slices", func() {
166+
s := make([]string, 0)
167+
expectToMarshal(struct{ S []string }{S: s}, `{"S":[]}`)
168+
expectToMarshal(struct{ S *[]string }{S: &s}, `{"S":[]}`)
169+
})
170+
171+
It("marshals nil slices", func() {
172+
var s []string
173+
expectToMarshal(struct{ S []string }{S: s}, `{"S":null}`)
174+
expectToMarshal(struct{ S *[]string }{S: &s}, `{"S":null}`)
175+
})
176+
})
177+
160178
Context("maps", func() {
161-
It("marshals maps with interface values", func() {
179+
It("marshals maps with interface{} values", func() {
162180
mi := map[string]interface{}{"foo": "hello", "bar": true, "baz": 42}
163181
expectToMarshal(struct{ M map[string]interface{} }{M: mi}, `{"M":{"foo":"hello","bar":true,"baz":42}}`)
164182
expectToMarshal(struct{ M *map[string]interface{} }{M: &mi}, `{"M":{"foo":"hello","bar":true,"baz":42}}`)

unmarshal.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ func unmarshalIntoSlice(ctx context.Context, target reflect.Value, found bool, s
167167
return nil
168168
}
169169

170+
if source == nil {
171+
return nil
172+
}
173+
170174
src, ok := source.([]interface{})
171175
if !ok {
172176
return newConversionError(ctx, source)

unmarshal_test.go

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -214,36 +214,100 @@ var _ = Describe("Unmarshal", func() {
214214
expectToFail(&s, `{"J":"foo"}`, `cannot unmarshal "foo" type "string" into field "J" (type "*int")`)
215215
})
216216

217-
It("unmarshals into a slice field", func() {
218-
var s struct {
219-
S []string
220-
N []int
221-
I []interface{}
222-
E []string
223-
}
224-
unmarshal(&s, `{"S":["a","b","c"],"N":[1,2,3],"I":["a",2,true]}`)
217+
It("rejects an array field", func() {
218+
var s struct{ S [3]string }
219+
expectToFail(&s, `{}`, `unsupported type "[3]string" at field "S" (type "[3]string")`)
220+
})
225221

226-
Expect(s).To(MatchAllFields(Fields{
227-
"S": Equal([]string{"a", "b", "c"}),
228-
"N": Equal([]int{1, 2, 3}),
229-
"I": Equal([]interface{}{"a", 2, true}),
230-
"E": BeEmpty(),
231-
}))
222+
Context("slices", func() {
223+
It("unmarshals into slices of interface{}", func() {
224+
By("slice", func() {
225+
var s struct{ I []interface{} }
226+
unmarshal(&s, `{"I": ["a",2,true]}`)
227+
Expect(s.I).To(Equal([]interface{}{"a", 2, true}))
228+
})
232229

233-
expectToFail(&s, `{"S":"foo"}`, `cannot unmarshal "foo" type "string" into field "S" (type "[]string")`)
234-
})
230+
By("pointer", func() {
231+
var s struct{ I *[]interface{} }
232+
unmarshal(&s, `{"I": ["a",2,true]}`)
233+
Expect(s.I).To(PointTo(Equal([]interface{}{"a", 2, true})))
234+
})
235+
})
235236

236-
It("unmarshals into a slice pointer field", func() {
237-
var s struct{ S *[]string }
238-
unmarshal(&s, `{"S":["a","b","c"]}`)
239-
Expect(s.S).To(PointTo(Equal([]string{"a", "b", "c"})))
237+
It("unmarshals into slices of string", func() {
238+
By("slice", func() {
239+
var s struct{ S []string }
240+
unmarshal(&s, `{"S":["a","b","c"]}`)
241+
Expect(s.S).To(Equal([]string{"a", "b", "c"}))
242+
})
240243

241-
expectToFail(&s, `{"S":"foo"}`, `cannot unmarshal "foo" type "string" into field "S" (type "*[]string")`)
242-
})
244+
By("pointer", func() {
245+
var s struct{ S *[]string }
246+
unmarshal(&s, `{"S":["a","b","c"]}`)
247+
Expect(s.S).To(PointTo(Equal([]string{"a", "b", "c"})))
248+
})
249+
})
243250

244-
It("rejects an array field", func() {
245-
var s struct{ S [3]string }
246-
expectToFail(&s, `{}`, `unsupported type "[3]string" at field "S" (type "[3]string")`)
251+
It("unmarshals into slices of int", func() {
252+
By("slice", func() {
253+
var s struct{ N []int }
254+
unmarshal(&s, `{"N":[1,2,3]}`)
255+
Expect(s.N).To(Equal([]int{1, 2, 3}))
256+
})
257+
258+
By("pointer", func() {
259+
var s struct{ N *[]int }
260+
unmarshal(&s, `{"N":[1,2,3]}`)
261+
Expect(s.N).To(PointTo(Equal([]int{1, 2, 3})))
262+
})
263+
})
264+
265+
It("unmarshals an omitted slice", func() {
266+
By("slice", func() {
267+
var s struct{ I []interface{} }
268+
unmarshal(&s, `{}`)
269+
Expect(s.I).To(BeNil())
270+
Expect(s.I).To(BeEmpty())
271+
})
272+
273+
By("pointer", func() {
274+
var s struct{ I *[]interface{} }
275+
unmarshal(&s, `{}`)
276+
Expect(s.I).To(BeNil())
277+
})
278+
})
279+
280+
It("unmarshals a null slice", func() {
281+
By("slice", func() {
282+
var s struct{ I []interface{} }
283+
unmarshal(&s, `{"I": null}`)
284+
Expect(s.I).To(BeNil())
285+
Expect(s.I).To(BeEmpty())
286+
})
287+
288+
By("pointer", func() {
289+
var s struct{ I *[]interface{} }
290+
unmarshal(&s, `{"I": null}`)
291+
Expect(s.I).To(BeNil())
292+
})
293+
})
294+
295+
It("unmarshals an empty slice", func() {
296+
By("slice", func() {
297+
var s struct{ I []interface{} }
298+
unmarshal(&s, `{"I": []}`)
299+
Expect(s.I).NotTo(BeNil())
300+
Expect(s.I).To(BeEmpty())
301+
})
302+
303+
By("pointer", func() {
304+
var s struct{ I *[]interface{} }
305+
unmarshal(&s, `{"I": []}`)
306+
Expect(s.I).NotTo(BeNil())
307+
Expect(s.I).To(PointTo(Not(BeNil())))
308+
Expect(s.I).To(PointTo(BeEmpty()))
309+
})
310+
})
247311
})
248312

249313
Context("maps", func() {
@@ -335,11 +399,11 @@ var _ = Describe("Unmarshal", func() {
335399
Expect(s.I).To(PointTo(BeEmpty()))
336400
})
337401
})
338-
})
339402

340-
It("rejects an map field that does not have string keys", func() {
341-
var s struct{ S map[int]string }
342-
expectToFail(&s, `{}`, `maps must only have string keys for "int" at field "S" (type "map[int]string")`)
403+
It("rejects an map field that does not have string keys", func() {
404+
var s struct{ S map[int]string }
405+
expectToFail(&s, `{}`, `maps must only have string keys for "int" at field "S" (type "map[int]string")`)
406+
})
343407
})
344408

345409
It("unmarshals into json.Unmarshaler field", func() {

0 commit comments

Comments
 (0)