@@ -193,6 +193,31 @@ func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error {
193
193
return json .NewEncoder (w ).Encode (payload )
194
194
}
195
195
196
+ func chooseFirstNonNilFieldValue (structValue reflect.Value ) (reflect.Value , error ) {
197
+ for i := 0 ; i < structValue .NumField (); i ++ {
198
+ choiceFieldValue := structValue .Field (i )
199
+ choiceTypeField := choiceFieldValue .Type ()
200
+
201
+ // Must be a pointer
202
+ if choiceTypeField .Kind () != reflect .Ptr {
203
+ continue
204
+ }
205
+
206
+ // Must not be nil
207
+ if choiceFieldValue .IsNil () {
208
+ continue
209
+ }
210
+
211
+ subtype := choiceTypeField .Elem ()
212
+ _ , err := jsonapiTypeOfModel (subtype )
213
+ if err == nil {
214
+ return choiceFieldValue , nil
215
+ }
216
+ }
217
+
218
+ return reflect.Value {}, errors .New ("no non-nil choice field was found in the specified struct" )
219
+ }
220
+
196
221
func visitModelNode (model interface {}, included * map [string ]* Node ,
197
222
sideload bool ) (* Node , error ) {
198
223
node := new (Node )
@@ -207,13 +232,13 @@ func visitModelNode(model interface{}, included *map[string]*Node,
207
232
modelType := value .Type ().Elem ()
208
233
209
234
for i := 0 ; i < modelValue .NumField (); i ++ {
235
+ fieldValue := modelValue .Field (i )
210
236
structField := modelValue .Type ().Field (i )
211
237
tag := structField .Tag .Get (annotationJSONAPI )
212
238
if tag == "" {
213
239
continue
214
240
}
215
241
216
- fieldValue := modelValue .Field (i )
217
242
fieldType := modelType .Field (i )
218
243
219
244
args := strings .Split (tag , annotationSeparator )
@@ -356,7 +381,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
356
381
node .Attributes [args [1 ]] = fieldValue .Interface ()
357
382
}
358
383
}
359
- } else if annotation == annotationRelation {
384
+ } else if annotation == annotationRelation || annotation == annotationPolyRelation {
360
385
var omitEmpty bool
361
386
362
387
//add support for 'omitempty' struct tag for marshaling as absent
@@ -371,6 +396,65 @@ func visitModelNode(model interface{}, included *map[string]*Node,
371
396
continue
372
397
}
373
398
399
+ if annotation == annotationPolyRelation {
400
+ // for polyrelation, we'll snoop out the actual relation model
401
+ // through the choice type value by choosing the first non-nil
402
+ // field that has a jsonapi type annotation and overwriting
403
+ // `fieldValue` so normal annotation-assisted marshaling
404
+ // can continue
405
+ if ! isSlice {
406
+ choiceValue := fieldValue
407
+
408
+ // must be a pointer type
409
+ if choiceValue .Type ().Kind () != reflect .Pointer {
410
+ er = ErrUnexpectedType
411
+ break
412
+ }
413
+
414
+ if choiceValue .IsNil () {
415
+ fieldValue = reflect .ValueOf (nil )
416
+ }
417
+
418
+ structValue := choiceValue .Elem ()
419
+ if found , err := chooseFirstNonNilFieldValue (structValue ); err == nil {
420
+ fieldValue = found
421
+ }
422
+ } else {
423
+ // A slice polyrelation field can be... polymorphic... meaning
424
+ // that we might snoop different types within each slice element.
425
+ // Each snooped value will added to this collection and then
426
+ // the recursion will take care of the rest. The only special case
427
+ // is nil. For that, we'll just choose the first
428
+ collection := make ([]interface {}, 0 )
429
+
430
+ for i := 0 ; i < fieldValue .Len (); i ++ {
431
+ itemValue := fieldValue .Index (i )
432
+ // Once again, must be a pointer type
433
+ if itemValue .Type ().Kind () != reflect .Pointer {
434
+ er = ErrUnexpectedType
435
+ break
436
+ }
437
+
438
+ if itemValue .IsNil () {
439
+ er = ErrUnexpectedNil
440
+ break
441
+ }
442
+
443
+ structValue := itemValue .Elem ()
444
+
445
+ if found , err := chooseFirstNonNilFieldValue (structValue ); err == nil {
446
+ collection = append (collection , found .Interface ())
447
+ }
448
+ }
449
+
450
+ if er != nil {
451
+ break
452
+ }
453
+
454
+ fieldValue = reflect .ValueOf (collection )
455
+ }
456
+ }
457
+
374
458
if node .Relationships == nil {
375
459
node .Relationships = make (map [string ]interface {})
376
460
}
0 commit comments