diff --git a/marshal.go b/marshal.go index 1cc6c6e..ed7683f 100644 --- a/marshal.go +++ b/marshal.go @@ -165,29 +165,24 @@ func makeDocument(v any, m *Marshaler, isRelationship bool) (*document, error) { // at this point we have no errors, so lets make the document d = newDocument() - if v == nil { - // if v is nil we want `{"data":null}` so return the empty document - return d, nil - } - - if reflect.ValueOf(v).IsZero() { - if reflect.TypeOf(v).Kind() == reflect.Slice { - // if v is an empty slice we want `{"data":[]}` - d.hasMany = true - } - return d, nil - } // the given "v" is the resource object (or a slice of them) // - // only a struct or slice of struct are valid here because we'll be parsing - // the jsonapi struct tags from the struct to make the resource object - + // besides nil, only a struct or slice of struct are valid here because + // we'll be parsing the jsonapi struct tags from the struct to make the + // resource object vt := reflect.TypeOf(v) - switch derefType(vt).Kind() { - case reflect.Slice: + switch { + case vt == nil: + // if v is nil we want `{"data":null}` so continue with an empty document + break + case derefType(vt).Kind() == reflect.Slice: // if we get a slice we make a resource object for each item d.hasMany = true + // if v is an empty slice we want `{"data":[]}` + if reflect.ValueOf(v).IsZero() { + break + } rv := derefValue(reflect.ValueOf(v)) for i := 0; i < rv.Len(); i++ { iv := rv.Index(i).Interface() @@ -199,7 +194,10 @@ func makeDocument(v any, m *Marshaler, isRelationship bool) (*document, error) { d.DataMany = append(d.DataMany, ro) } } - case reflect.Struct: + case derefType(vt).Kind() == reflect.Struct: + if reflect.ValueOf(v).IsZero() { + break + } // if we get a struct we just make a single resource object ro, err := makeResourceObject(v, vt, m, isRelationship) if err != nil { diff --git a/marshal_test.go b/marshal_test.go index e10820e..c5fe399 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -36,6 +36,11 @@ func TestMarshal(t *testing.T) { given: new(Article), expect: "", expectError: ErrEmptyPrimaryField, + }, { + description: "[]*Article (nil)", + given: []*Article(nil), + expect: emptyBody, + expectError: nil, }, { description: "[]*Article (empty)", given: make([]*Article, 0), @@ -295,6 +300,8 @@ func TestMarshalMeta(t *testing.T) { t.Parallel() articleAMetaBody := `{"data":{"id":"1","type":"articles","attributes":{"title":"A"}},"meta":{"foo":"bar"}}` + articleAMetaNullBody := `{"data": null,"meta":{"foo":"bar"}}` + articleAMetaEmptyBody := `{"data":[],"meta":{"foo":"bar"}}` errorsObjectMetaBody := `{"meta":{"foo":"bar"},"errors":[{"title":"T"}]}` tests := []struct { @@ -330,6 +337,24 @@ func TestMarshalMeta(t *testing.T) { givenMeta: map[string]any{"foo": "bar"}, expect: errorsObjectMetaBody, expectError: nil, + }, { + description: "map[string]any with nil body", + given: nil, + givenMeta: map[string]any{"foo": "bar"}, + expect: articleAMetaNullBody, + expectError: nil, + }, { + description: "map[string]any with Article (empty)", + given: Article{}, + givenMeta: map[string]any{"foo": "bar"}, + expect: articleAMetaNullBody, + expectError: nil, + }, { + description: "map[string]any with body []*Article (empty)", + given: []*Article(nil), + givenMeta: map[string]any{"foo": "bar"}, + expect: articleAMetaEmptyBody, + expectError: nil, }, } @@ -741,5 +766,4 @@ func TestMarshalMemberNameValidation(t *testing.T) { is.MustNoError(t, err) }) } - }