Skip to content

Commit

Permalink
Marshal embedded structs and struct pointers (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Delerme authored Feb 28, 2023
1 parent 283c5cf commit 20e618e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 5 deletions.
21 changes: 21 additions & 0 deletions jsonapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ var (
articleAIntIDID = ArticleIntIDID{ID: IntID(1), Title: "A"}
articleBIntIDID = ArticleIntIDID{ID: IntID(2), Title: "B"}
articlesIntIDIDABPtr = []*ArticleIntIDID{&articleAIntIDID, &articleBIntIDID}
articleEmbedded = ArticleEmbedded{ID: "1", Title: "A", Metadata: Metadata{LastModified: time.Date(1989, 06, 15, 0, 0, 0, 0, time.UTC)}}
articleEmbeddedPointer = ArticleEmbeddedPointer{ID: "1", Title: "A", Metadata: &Metadata{LastModified: time.Date(1989, 06, 15, 0, 0, 0, 0, time.UTC)}}

// articles with optional meta
articleAWithMeta = ArticleWithMeta{ID: "1", Title: "A", Meta: &ArticleMetrics{Views: 10, Reads: 4}}
Expand Down Expand Up @@ -86,6 +88,7 @@ var (
articleLinkedOnlySelfBody = `{"data":{"id":"1","type":"articles","links":{"self":"https://example.com/articles/1"}}}`
articleWithResourceObjectMetaBody = `{"data":{"type":"articles","id":"1","attributes":{"title":"A"},"meta":{"count":10}}}`
articleAWithMetaBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"meta":{"views":10,"reads":4}}}`
articleEmbeddedBody = `{"data":{"type":"articles","id":"1","attributes":{"title":"A","lastModified":"1989-06-15T00:00:00Z"}}}`

// articles with relationships bodies
articleRelatedNoOmitEmptyBody = `{"data":{"id":"1","type":"articles","attributes":{"title":"A"},"relationships":{"author":{"data":null},"comments":{"data":[]}}}}`
Expand Down Expand Up @@ -327,3 +330,21 @@ type ArticleDoubleID struct {
Title string `jsonapi:"attribute" json:"title"`
OtherID string `jsonapi:"primary,article"`
}

type Metadata struct {
LastModified time.Time `jsonapi:"attribute" json:"lastModified"`
}

type ArticleEmbedded struct {
Metadata

ID string `jsonapi:"primary,articles"`
Title string `jsonapi:"attribute" json:"title"`
}

type ArticleEmbeddedPointer struct {
*Metadata

ID string `jsonapi:"primary,articles"`
Title string `jsonapi:"attribute" json:"title"`
}
39 changes: 34 additions & 5 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,16 +298,16 @@ func makeResourceObject(v any, vt reflect.Type, m *Marshaler, isRelationship boo
Relationships: make(map[string]*document, 0),
}

rv := derefValue(reflect.ValueOf(v))
rt := reflect.TypeOf(rv.Interface())
// get fields from embedded structs
fields := getFlattenedFields(v)

var foundPrimary bool
for i := 0; i < rv.NumField(); i++ {
for _, field := range fields {
// for each field in the struct we'll parse the jsonapi struct tag
// this will determine where it goes in the resource object (e.g. id,type,attributes,...)

f := rv.Field(i)
ft := rt.Field(i)
f := field.v
ft := field.f

tag, err := parseJSONAPITag(ft)
if err != nil {
Expand Down Expand Up @@ -434,6 +434,35 @@ func makeResourceObject(v any, vt reflect.Type, m *Marshaler, isRelationship boo
return ro, nil
}

func getFlattenedFields(iface interface{}) []struct {
v reflect.Value
f reflect.StructField
} {
rv := derefValue(reflect.ValueOf(iface))
rt := reflect.TypeOf(rv.Interface())

fields := make([]struct {
v reflect.Value
f reflect.StructField
}, 0)

for i := 0; i < rv.NumField(); i++ {
v := rv.Field(i)
f := rt.Field(i)

if f.Anonymous && (v.Kind() == reflect.Struct || v.Kind() == reflect.Pointer) {
fields = append(fields, getFlattenedFields(v.Interface())...)
} else {
fields = append(fields, struct {
v reflect.Value
f reflect.StructField
}{v, f})
}
}

return fields
}

func addOptionalDocumentFields(d *document, m *Marshaler) error {
// optionally include Document.meta (may be nil, which will be omitted)
if err := checkMeta(m.meta); err != nil {
Expand Down
10 changes: 10 additions & 0 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ func TestMarshal(t *testing.T) {
given: &articleWithoutResourceObjectMeta,
expect: articleABody,
expectError: nil,
}, {
description: "ArticleEmbedded",
given: &articleEmbedded,
expect: articleEmbeddedBody,
expectError: nil,
}, {
description: "ArticleEmbeddedPointer",
given: &articleEmbeddedPointer,
expect: articleEmbeddedBody,
expectError: nil,
}, {
description: "Error simple",
given: errorsSimpleStruct,
Expand Down
20 changes: 20 additions & 0 deletions unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ func TestUnmarshal(t *testing.T) {
},
expect: &articleAWithMeta,
expectError: nil,
}, {
description: "ArticleEmbedded",
given: articleEmbeddedBody,
do: func(body []byte) (any, error) {
var a ArticleEmbedded
err := Unmarshal(body, &a)
return &a, err
},
expect: &articleEmbedded,
expectError: nil,
}, {
description: "ArticleEmbeddedPointer",
given: articleEmbeddedBody,
do: func(body []byte) (any, error) {
var a ArticleEmbeddedPointer
err := Unmarshal(body, &a)
return &a, err
},
expect: &articleEmbeddedPointer,
expectError: nil,
}, {
description: "nil",
given: "",
Expand Down

0 comments on commit 20e618e

Please sign in to comment.