Skip to content

Commit 7fcf2ea

Browse files
Add support for unmarshalling embedded structs with relationships (#52)
* Add support for unmarshalling embedded structs with relationships This commit adds better support for unmarshaling embedded structs that contain relationship fields --------- Co-authored-by: David Sevilla <[email protected]>
1 parent 255b6ab commit 7fcf2ea

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

jsonapi_test.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@ var (
1515
authorBWithMeta = Author{ID: "2", Name: "B", Meta: map[string]any{"count": 10.0}}
1616

1717
// comments
18-
commentA = Comment{ID: "1", Body: "A"}
19-
commentB = Comment{ID: "2", Body: "B"}
20-
commentAWithAuthor = Comment{ID: "1", Body: "A", Author: &authorA}
21-
commentArchived = Comment{ID: "1", Body: "A", Archived: true}
22-
commentsAB = []*Comment{&commentA, &commentB}
18+
commentA = Comment{ID: "1", Body: "A"}
19+
commentB = Comment{ID: "2", Body: "B"}
20+
commentAWithAuthor = Comment{ID: "1", Body: "A", Author: &authorA}
21+
commentArchived = Comment{ID: "1", Body: "A", Archived: true}
22+
commentsAB = []*Comment{&commentA, &commentB}
23+
commentEmbeddedFields = CommentFields{Body: "A", Author: Author{ID: "1"}}
24+
commentEmbedded = CommentEmbedded{ID: "1", CommentFields: commentEmbeddedFields}
25+
26+
commentEmbeddedFieldsPointer = CommentFieldsPointer{Body: "A", Author: &Author{ID: "1"}}
27+
commentEmbeddedPointer = CommentEmbeddedPointer{ID: "1", CommentFieldsPointer: commentEmbeddedFieldsPointer}
2328

2429
// articles
2530
articleA = Article{ID: "1", Title: "A"}
@@ -157,6 +162,7 @@ var (
157162
articleNullWithToplevelMetaBody = `{"data":null,"meta":{"foo":"bar"}}`
158163
articleEmptyArrayWithToplevelMetaBody = `{"data":[],"meta":{"foo":"bar"}}`
159164
articleEmbeddedBody = `{"data":{"type":"articles","id":"1","attributes":{"title":"A","lastModified":"1989-06-15T00:00:00Z"}}}`
165+
commentEmbeddedBody = `{"data":{"id":"1","type":"comments","attributes":{"body":"A"},"relationships":{"author":{"data":{"id":"1","type":"author"}}}}}`
160166

161167
// articles with relationships bodies
162168
articleRelatedInvalidEmptyRelationshipBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{}}}}`
@@ -454,6 +460,28 @@ type Metadata struct {
454460
LastModified time.Time `jsonapi:"attribute" json:"lastModified"`
455461
}
456462

463+
type CommentFields struct {
464+
Body string `jsonapi:"attribute" json:"body"`
465+
Archived bool `jsonapi:"attribute" json:"archived,omitempty"`
466+
Author Author `jsonapi:"relationship" json:"author,omitempty"`
467+
}
468+
469+
type CommentFieldsPointer struct {
470+
Body string `jsonapi:"attribute" json:"body"`
471+
Archived bool `jsonapi:"attribute" json:"archived,omitempty"`
472+
Author *Author `jsonapi:"relationship" json:"author,omitempty"`
473+
}
474+
475+
type CommentEmbedded struct {
476+
ID string `jsonapi:"primary,comments"`
477+
CommentFields
478+
}
479+
480+
type CommentEmbeddedPointer struct {
481+
ID string `jsonapi:"primary,comments"`
482+
CommentFieldsPointer
483+
}
484+
457485
type ArticleEmbedded struct {
458486
Metadata
459487

unmarshal.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,18 @@ func (ro *resourceObject) unmarshal(v any, m *Unmarshaler) error {
170170
return &TypeError{Actual: vt.String(), Expected: []string{"struct"}}
171171
}
172172

173-
if err := ro.unmarshalFields(v, m); err != nil {
173+
rv := derefValue(reflect.ValueOf(v))
174+
rt := reflect.TypeOf(rv.Interface())
175+
if err := ro.unmarshalFields(v, rv, rt, m); err != nil {
174176
return err
175177
}
176178

177179
return ro.unmarshalAttributes(v)
178180
}
179181

180182
// unmarshalFields unmarshals a resource object into all non-attribute struct fields
181-
func (ro *resourceObject) unmarshalFields(v any, m *Unmarshaler) error {
183+
func (ro *resourceObject) unmarshalFields(v any, rv reflect.Value, rt reflect.Type, m *Unmarshaler) error {
182184
setPrimary := false
183-
rv := derefValue(reflect.ValueOf(v))
184-
rt := reflect.TypeOf(rv.Interface())
185185

186186
for i := 0; i < rv.NumField(); i++ {
187187
fv := rv.Field(i)
@@ -192,6 +192,11 @@ func (ro *resourceObject) unmarshalFields(v any, m *Unmarshaler) error {
192192
return err
193193
}
194194
if jsonapiTag == nil {
195+
if ft.Anonymous && fv.Kind() == reflect.Struct {
196+
if err := ro.unmarshalFields(v, fv, reflect.TypeOf(fv.Interface()), m); err != nil {
197+
return err
198+
}
199+
}
195200
continue
196201
}
197202

unmarshal_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,27 @@ func TestUnmarshal(t *testing.T) {
446446
},
447447
expect: &Article{},
448448
expectError: ErrErrorUnmarshalingNotImplemented,
449+
}, {
450+
description: "CommentEmbedded",
451+
given: commentEmbeddedBody,
452+
do: func(body []byte) (any, error) {
453+
var a CommentEmbedded
454+
err := Unmarshal(body, &a)
455+
return &a, err
456+
},
457+
expect: &commentEmbedded,
458+
expectError: nil,
459+
},
460+
{
461+
description: "CommentEmbeddedPointer",
462+
given: commentEmbeddedBody,
463+
do: func(body []byte) (any, error) {
464+
var a CommentEmbeddedPointer
465+
err := Unmarshal(body, &a)
466+
return &a, err
467+
},
468+
expect: &commentEmbeddedPointer,
469+
expectError: nil,
449470
},
450471
{
451472
description: "ArticleLinkedOnlySelf",

0 commit comments

Comments
 (0)