@@ -221,6 +221,18 @@ func selectChoiceTypeStructField(structValue reflect.Value) (reflect.Value, erro
221
221
return reflect.Value {}, errors .New ("no non-nil choice field was found in the specified struct" )
222
222
}
223
223
224
+ // hasJSONAPIAnnotations returns true if any of the fields of a struct type t
225
+ // has a jsonapi annotation. This function will panic if t is not a struct type.
226
+ func hasJSONAPIAnnotations (t reflect.Type ) bool {
227
+ for i := 0 ; i < t .NumField (); i ++ {
228
+ tag := t .Field (i ).Tag .Get (annotationJSONAPI )
229
+ if tag != "" {
230
+ return true
231
+ }
232
+ }
233
+ return false
234
+ }
235
+
224
236
func visitModelNodeAttribute (args []string , node * Node , fieldValue reflect.Value ) error {
225
237
var omitEmpty , iso8601 , rfc3339 bool
226
238
@@ -314,31 +326,56 @@ func visitModelNodeAttribute(args []string, node *Node, fieldValue reflect.Value
314
326
if fieldValue .Len () == 0 && omitEmpty {
315
327
return nil
316
328
}
317
- // Nested slice of object attributes
318
- manyNested , err := visitModelNodeRelationships (fieldValue , nil , false )
319
- if err != nil {
320
- return fmt .Errorf ("failed to marshal slice of nested attribute %q: %w" , args [1 ], err )
329
+
330
+ var t reflect.Type
331
+ if isSliceOfStruct {
332
+ t = fieldValue .Type ().Elem ()
333
+ } else {
334
+ t = fieldValue .Type ().Elem ().Elem ()
321
335
}
322
- nestedNodes := make ([]any , len (manyNested .Data ))
323
- for i , n := range manyNested .Data {
324
- nestedNodes [i ] = n .Attributes
336
+
337
+ // This check is to maintain backwards compatibility with `json` annotated
338
+ // nested structs, which should fall through to "primitive" handling below
339
+ if hasJSONAPIAnnotations (t ) {
340
+ // Nested slice of object attributes
341
+ manyNested , err := visitModelNodeRelationships (fieldValue , nil , false )
342
+ if err != nil {
343
+ return fmt .Errorf ("failed to marshal slice of nested attribute %q: %w" , args [1 ], err )
344
+ }
345
+ nestedNodes := make ([]any , len (manyNested .Data ))
346
+ for i , n := range manyNested .Data {
347
+ nestedNodes [i ] = n .Attributes
348
+ }
349
+ node .Attributes [args [1 ]] = nestedNodes
350
+ return nil
325
351
}
326
- node .Attributes [args [1 ]] = nestedNodes
327
352
} else if isStruct || isPointerToStruct {
328
- // Nested object attribute
329
- nested , err := visitModelNode (fieldValue .Interface (), nil , false )
330
- if err != nil {
331
- return fmt .Errorf ("failed to marshal nested attribute %q: %w" , args [1 ], err )
332
- }
333
- node .Attributes [args [1 ]] = nested .Attributes
334
- } else {
335
- // Primitive attribute
336
- strAttr , ok := fieldValue .Interface ().(string )
337
- if ok {
338
- node .Attributes [args [1 ]] = strAttr
353
+ var t reflect.Type
354
+ if isStruct {
355
+ t = fieldValue .Type ()
339
356
} else {
340
- node . Attributes [ args [ 1 ]] = fieldValue .Interface ()
357
+ t = fieldValue .Type (). Elem ()
341
358
}
359
+
360
+ // This check is to maintain backwards compatibility with `json` annotated
361
+ // nested structs, which should fall through to "primitive" handling below
362
+ if hasJSONAPIAnnotations (t ) {
363
+ // Nested object attribute
364
+ nested , err := visitModelNode (fieldValue .Interface (), nil , false )
365
+ if err != nil {
366
+ return fmt .Errorf ("failed to marshal nested attribute %q: %w" , args [1 ], err )
367
+ }
368
+ node .Attributes [args [1 ]] = nested .Attributes
369
+ return nil
370
+ }
371
+ }
372
+
373
+ // Primitive attribute
374
+ strAttr , ok := fieldValue .Interface ().(string )
375
+ if ok {
376
+ node .Attributes [args [1 ]] = strAttr
377
+ } else {
378
+ node .Attributes [args [1 ]] = fieldValue .Interface ()
342
379
}
343
380
}
344
381
0 commit comments