Skip to content

Commit

Permalink
Add support for unmarshalling embedded structs with relationships (#52)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
kevinconaway and DQSevilla authored Mar 14, 2024
1 parent 255b6ab commit 7fcf2ea
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 9 deletions.
38 changes: 33 additions & 5 deletions jsonapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ var (
authorBWithMeta = Author{ID: "2", Name: "B", Meta: map[string]any{"count": 10.0}}

// comments
commentA = Comment{ID: "1", Body: "A"}
commentB = Comment{ID: "2", Body: "B"}
commentAWithAuthor = Comment{ID: "1", Body: "A", Author: &authorA}
commentArchived = Comment{ID: "1", Body: "A", Archived: true}
commentsAB = []*Comment{&commentA, &commentB}
commentA = Comment{ID: "1", Body: "A"}
commentB = Comment{ID: "2", Body: "B"}
commentAWithAuthor = Comment{ID: "1", Body: "A", Author: &authorA}
commentArchived = Comment{ID: "1", Body: "A", Archived: true}
commentsAB = []*Comment{&commentA, &commentB}
commentEmbeddedFields = CommentFields{Body: "A", Author: Author{ID: "1"}}
commentEmbedded = CommentEmbedded{ID: "1", CommentFields: commentEmbeddedFields}

commentEmbeddedFieldsPointer = CommentFieldsPointer{Body: "A", Author: &Author{ID: "1"}}
commentEmbeddedPointer = CommentEmbeddedPointer{ID: "1", CommentFieldsPointer: commentEmbeddedFieldsPointer}

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

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

type CommentFields struct {
Body string `jsonapi:"attribute" json:"body"`
Archived bool `jsonapi:"attribute" json:"archived,omitempty"`
Author Author `jsonapi:"relationship" json:"author,omitempty"`
}

type CommentFieldsPointer struct {
Body string `jsonapi:"attribute" json:"body"`
Archived bool `jsonapi:"attribute" json:"archived,omitempty"`
Author *Author `jsonapi:"relationship" json:"author,omitempty"`
}

type CommentEmbedded struct {
ID string `jsonapi:"primary,comments"`
CommentFields
}

type CommentEmbeddedPointer struct {
ID string `jsonapi:"primary,comments"`
CommentFieldsPointer
}

type ArticleEmbedded struct {
Metadata

Expand Down
13 changes: 9 additions & 4 deletions unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,18 @@ func (ro *resourceObject) unmarshal(v any, m *Unmarshaler) error {
return &TypeError{Actual: vt.String(), Expected: []string{"struct"}}
}

if err := ro.unmarshalFields(v, m); err != nil {
rv := derefValue(reflect.ValueOf(v))
rt := reflect.TypeOf(rv.Interface())
if err := ro.unmarshalFields(v, rv, rt, m); err != nil {
return err
}

return ro.unmarshalAttributes(v)
}

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

for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i)
Expand All @@ -192,6 +192,11 @@ func (ro *resourceObject) unmarshalFields(v any, m *Unmarshaler) error {
return err
}
if jsonapiTag == nil {
if ft.Anonymous && fv.Kind() == reflect.Struct {
if err := ro.unmarshalFields(v, fv, reflect.TypeOf(fv.Interface()), m); err != nil {
return err
}
}
continue
}

Expand Down
21 changes: 21 additions & 0 deletions unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,27 @@ func TestUnmarshal(t *testing.T) {
},
expect: &Article{},
expectError: ErrErrorUnmarshalingNotImplemented,
}, {
description: "CommentEmbedded",
given: commentEmbeddedBody,
do: func(body []byte) (any, error) {
var a CommentEmbedded
err := Unmarshal(body, &a)
return &a, err
},
expect: &commentEmbedded,
expectError: nil,
},
{
description: "CommentEmbeddedPointer",
given: commentEmbeddedBody,
do: func(body []byte) (any, error) {
var a CommentEmbeddedPointer
err := Unmarshal(body, &a)
return &a, err
},
expect: &commentEmbeddedPointer,
expectError: nil,
},
{
description: "ArticleLinkedOnlySelf",
Expand Down

0 comments on commit 7fcf2ea

Please sign in to comment.