@@ -174,6 +174,172 @@ func TestApplyDefaults(t *testing.T) {
174174 }
175175}
176176
177+ func TestApplyNestedDefaults (t * testing.T ) {
178+ base := & Schema {
179+ Type : "object" ,
180+ Properties : map [string ]* Schema {
181+ "A" : {
182+ Type : "object" ,
183+ Properties : map [string ]* Schema {
184+ "B" : {Type : "string" , Default : mustMarshal ("foo" )},
185+ },
186+ },
187+ },
188+ }
189+ // Variant where parent A has its own default object; recursion should still fill B.
190+ withParentDefault := & Schema {
191+ Type : "object" ,
192+ Properties : map [string ]* Schema {
193+ "A" : {
194+ Type : "object" ,
195+ Default : mustMarshal (map [string ]any {"X" : 1 }),
196+ Properties : map [string ]* Schema {
197+ "B" : {Type : "string" , Default : mustMarshal ("foo" )},
198+ },
199+ },
200+ },
201+ }
202+
203+ type Nested struct { B string }
204+ type Root struct { A Nested }
205+ type RootPtr struct { A * Nested }
206+ type RootMap struct { A map [string ]any }
207+ type RootPtrMap struct { A * map [string ]any }
208+
209+ mapPtr := func (m map [string ]any ) * map [string ]any { return & m }
210+
211+ for _ , tc := range []struct {
212+ name string
213+ schema * Schema
214+ instancep any
215+ want any
216+ }{
217+ {
218+ name : "MapMissingParent" ,
219+ schema : base ,
220+ instancep : & map [string ]any {},
221+ want : map [string ]any {"A" : map [string ]any {"B" : "foo" }},
222+ },
223+ {
224+ name : "MapEmptyParent" ,
225+ schema : base ,
226+ instancep : & map [string ]any {"A" : map [string ]any {}},
227+ want : map [string ]any {"A" : map [string ]any {"B" : "foo" }},
228+ },
229+ {
230+ name : "MapParentHasDefaultObjectMissing" ,
231+ schema : withParentDefault ,
232+ instancep : & map [string ]any {},
233+ want : map [string ]any {"A" : map [string ]any {"X" : float64 (1 ), "B" : "foo" }},
234+ },
235+ {
236+ name : "MapParentHasDefaultObjectPresent" ,
237+ schema : withParentDefault ,
238+ instancep : & map [string ]any {"A" : map [string ]any {}},
239+ // Parent default is applied only when the property is missing, so
240+ // with the key present (even if empty), we apply only the nested defaults.
241+ want : map [string ]any {"A" : map [string ]any {"B" : "foo" }},
242+ },
243+ {
244+ name : "MapValueMapMissingParentTyped" ,
245+ schema : base ,
246+ instancep : & map [string ]map [string ]any {},
247+ want : map [string ]map [string ]any {"A" : {"B" : "foo" }},
248+ },
249+ {
250+ name : "MapValueStructMissingParentTyped" ,
251+ schema : base ,
252+ instancep : & map [string ]Nested {},
253+ want : map [string ]Nested {"A" : {B : "foo" }},
254+ },
255+ {
256+ name : "StructZeroValueParent" ,
257+ schema : base ,
258+ instancep : & Root {},
259+ want : Root {A : Nested {B : "foo" }},
260+ },
261+ {
262+ name : "StructZeroValueParentWithParentDefault" ,
263+ schema : withParentDefault ,
264+ instancep : & Root {},
265+ want : Root {A : Nested {B : "foo" }},
266+ },
267+ {
268+ name : "StructPointerNilParent" ,
269+ schema : base ,
270+ instancep : & RootPtr {},
271+ want : RootPtr {A : & Nested {B : "foo" }},
272+ },
273+ {
274+ name : "StructPresentNonzeroChildPreserved" ,
275+ schema : base ,
276+ instancep : & Root {A : Nested {B : "bar" }},
277+ want : Root {A : Nested {B : "bar" }},
278+ },
279+ {
280+ name : "StructPointerNonNilChildPreserved" ,
281+ schema : base ,
282+ instancep : & RootPtr {A : & Nested {B : "bar" }},
283+ want : RootPtr {A : & Nested {B : "bar" }},
284+ },
285+ {
286+ name : "StructMapNilParent" ,
287+ schema : base ,
288+ instancep : & RootMap {},
289+ want : RootMap {A : map [string ]any {"B" : "foo" }},
290+ },
291+ {
292+ name : "StructMapParentDefaultObjectMissing" ,
293+ schema : withParentDefault ,
294+ instancep : & RootMap {},
295+ want : RootMap {A : map [string ]any {"X" : float64 (1 ), "B" : "foo" }},
296+ },
297+ {
298+ name : "StructMapParentDefaultObjectPresent" ,
299+ schema : withParentDefault ,
300+ instancep : & RootMap {A : map [string ]any {}},
301+ want : RootMap {A : map [string ]any {"B" : "foo" }},
302+ },
303+ {
304+ name : "StructPtrMapNilParent" ,
305+ schema : base ,
306+ instancep : & RootPtrMap {},
307+ want : RootPtrMap {A : mapPtr (map [string ]any {"B" : "foo" })},
308+ },
309+ {
310+ name : "StructMapNilParentWithNullParentDefault" ,
311+ schema : & Schema {
312+ Type : "object" ,
313+ Properties : map [string ]* Schema {
314+ "A" : {
315+ // Default null exercises map allocation in struct subschemas
316+ Default : mustMarshal (nil ),
317+ Properties : map [string ]* Schema {
318+ "B" : {Type : "string" , Default : mustMarshal ("foo" )},
319+ },
320+ },
321+ },
322+ },
323+ instancep : & RootMap {},
324+ want : RootMap {A : map [string ]any {"B" : "foo" }},
325+ },
326+ } {
327+ t .Run (tc .name , func (t * testing.T ) {
328+ rs , err := tc .schema .Resolve (& ResolveOptions {ValidateDefaults : true })
329+ if err != nil {
330+ t .Fatal (err )
331+ }
332+ if err := rs .ApplyDefaults (tc .instancep ); err != nil {
333+ t .Fatal (err )
334+ }
335+ got := reflect .ValueOf (tc .instancep ).Elem ().Interface ()
336+ if ! reflect .DeepEqual (got , tc .want ) {
337+ t .Errorf ("nested defaults:\n got %#v\n want %#v" , got , tc .want )
338+ }
339+ })
340+ }
341+ }
342+
177343func TestStructInstance (t * testing.T ) {
178344 instance := struct {
179345 I int
0 commit comments