Skip to content

Commit cbd4540

Browse files
authored
GODRIVER-1235 Skip embedded documents and arrays correctly in extJSONValueReader (#544)
1 parent b88a5d7 commit cbd4540

File tree

2 files changed

+125
-45
lines changed

2 files changed

+125
-45
lines changed

bson/bsonrw/extjson_reader.go

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -159,29 +159,18 @@ func (ejvr *extJSONValueReader) pop() {
159159
}
160160
}
161161

162-
func (ejvr *extJSONValueReader) skipDocument() error {
163-
// read entire document until ErrEOD (using readKey and readValue)
164-
_, typ, err := ejvr.p.readKey()
165-
for err == nil {
166-
_, err = ejvr.p.readValue(typ)
167-
if err != nil {
168-
break
162+
func (ejvr *extJSONValueReader) skipObject() {
163+
// read entire object until depth returns to 0 (last ending } or ] seen)
164+
depth := 1
165+
for depth > 0 {
166+
ejvr.p.advanceState()
167+
switch ejvr.p.s {
168+
case jpsSawBeginObject, jpsSawBeginArray:
169+
depth++
170+
case jpsSawEndObject, jpsSawEndArray:
171+
depth--
169172
}
170-
171-
_, typ, err = ejvr.p.readKey()
172173
}
173-
174-
return err
175-
}
176-
177-
func (ejvr *extJSONValueReader) skipArray() error {
178-
// read entire array until ErrEOA (using peekType)
179-
_, err := ejvr.p.peekType()
180-
for err == nil {
181-
_, err = ejvr.p.peekType()
182-
}
183-
184-
return err
185174
}
186175

187176
func (ejvr *extJSONValueReader) invalidTransitionErr(destination mode, name string, modes []mode) error {
@@ -234,30 +223,9 @@ func (ejvr *extJSONValueReader) Skip() error {
234223

235224
t := ejvr.stack[ejvr.frame].vType
236225
switch t {
237-
case bsontype.Array:
238-
// read entire array until ErrEOA
239-
err := ejvr.skipArray()
240-
if err != ErrEOA {
241-
return err
242-
}
243-
case bsontype.EmbeddedDocument:
244-
// read entire doc until ErrEOD
245-
err := ejvr.skipDocument()
246-
if err != ErrEOD {
247-
return err
248-
}
249-
case bsontype.CodeWithScope:
250-
// read the code portion and set up parser in document mode
251-
_, err := ejvr.p.readValue(t)
252-
if err != nil {
253-
return err
254-
}
255-
256-
// read until ErrEOD
257-
err = ejvr.skipDocument()
258-
if err != ErrEOD {
259-
return err
260-
}
226+
case bsontype.Array, bsontype.EmbeddedDocument, bsontype.CodeWithScope:
227+
// read entire array, doc or CodeWithScope
228+
ejvr.skipObject()
261229
default:
262230
_, err := ejvr.p.readValue(t)
263231
if err != nil {

bson/unmarshal_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,115 @@ func TestCachingDecodersNotSharedAcrossRegistries(t *testing.T) {
166166
assert.Equal(t, int32(-1), *second.X, "expected X value to be -1, got %v", *second.X)
167167
})
168168
}
169+
170+
func TestUnmarshalExtJSONWithUndefinedField(t *testing.T) {
171+
// When unmarshalling, fields that are undefined in the destination struct are skipped.
172+
// This process must not skip other, defined fields and must not raise errors.
173+
type expectedResponse struct {
174+
DefinedField string
175+
}
176+
177+
unmarshalExpectedResponse := func(t *testing.T, extJSON string) *expectedResponse {
178+
t.Helper()
179+
responseDoc := expectedResponse{}
180+
err := UnmarshalExtJSON([]byte(extJSON), false, &responseDoc)
181+
assert.Nil(t, err, "UnmarshalExtJSON error: %v", err)
182+
return &responseDoc
183+
}
184+
185+
testCases := []struct {
186+
name string
187+
testJSON string
188+
}{
189+
{
190+
"no array",
191+
`{
192+
"UndefinedField": {"key": 1},
193+
"DefinedField": "value"
194+
}`,
195+
},
196+
{
197+
"outer array",
198+
`{
199+
"UndefinedField": [{"key": 1}],
200+
"DefinedField": "value"
201+
}`,
202+
},
203+
{
204+
"embedded array",
205+
`{
206+
"UndefinedField": {"keys": [2]},
207+
"DefinedField": "value"
208+
}`,
209+
},
210+
{
211+
"outer array and embedded array",
212+
`{
213+
"UndefinedField": [{"keys": [2]}],
214+
"DefinedField": "value"
215+
}`,
216+
},
217+
{
218+
"embedded document",
219+
`{
220+
"UndefinedField": {"key": {"one": "two"}},
221+
"DefinedField": "value"
222+
}`,
223+
},
224+
{
225+
"doubly embedded document",
226+
`{
227+
"UndefinedField": {"key": {"one": {"two": "three"}}},
228+
"DefinedField": "value"
229+
}`,
230+
},
231+
{
232+
"embedded document and embedded array",
233+
`{
234+
"UndefinedField": {"key": {"one": {"two": [3]}}},
235+
"DefinedField": "value"
236+
}`,
237+
},
238+
{
239+
"embedded document and embedded array in outer array",
240+
`{
241+
"UndefinedField": [{"key": {"one": [3]}}],
242+
"DefinedField": "value"
243+
}`,
244+
},
245+
{
246+
"code with scope",
247+
`{
248+
"UndefinedField": {"logic": {"$code": "foo", "$scope": {"bar": 1}}},
249+
"DefinedField": "value"
250+
}`,
251+
},
252+
{
253+
"embedded array of code with scope",
254+
`{
255+
"UndefinedField": {"logic": [{"$code": "foo", "$scope": {"bar": 1}}]},
256+
"DefinedField": "value"
257+
}`,
258+
},
259+
{
260+
"type definition embedded document",
261+
`{
262+
"UndefinedField": {"myDouble": {"$numberDouble": "1.24"}},
263+
"DefinedField": "value"
264+
}`,
265+
},
266+
{
267+
"empty embedded document",
268+
`{
269+
"UndefinedField": {"empty": {}},
270+
"DefinedField": "value"
271+
}`,
272+
},
273+
}
274+
for _, tc := range testCases {
275+
t.Run(tc.name, func(t *testing.T) {
276+
responseDoc := unmarshalExpectedResponse(t, tc.testJSON)
277+
assert.Equal(t, "value", responseDoc.DefinedField, "expected DefinedField to be 'value', got %q", responseDoc.DefinedField)
278+
})
279+
}
280+
}

0 commit comments

Comments
 (0)