@@ -221,6 +221,18 @@ func selectChoiceTypeStructField(structValue reflect.Value) (reflect.Value, erro
221221 return reflect.Value {}, errors .New ("no non-nil choice field was found in the specified struct" )
222222}
223223
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+
224236func visitModelNodeAttribute (args []string , node * Node , fieldValue reflect.Value ) error {
225237 var omitEmpty , iso8601 , rfc3339 bool
226238
@@ -314,31 +326,56 @@ func visitModelNodeAttribute(args []string, node *Node, fieldValue reflect.Value
314326 if fieldValue .Len () == 0 && omitEmpty {
315327 return nil
316328 }
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 ()
321335 }
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
325351 }
326- node .Attributes [args [1 ]] = nestedNodes
327352 } 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 ()
339356 } else {
340- node . Attributes [ args [ 1 ]] = fieldValue .Interface ()
357+ t = fieldValue .Type (). Elem ()
341358 }
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 ()
342379 }
343380 }
344381
0 commit comments