diff --git a/.circleci/config.yml b/.circleci/config.yml index 0f63952..a21cb1a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: #### expecting it in the form of #### /go/src/github.com/circleci/go-tool #### /go/src/bitbucket.org/circleci/go-tool - working_directory: /go/src/github.com/moltin/jsonapi + working_directory: /go/src/github.com/elasticpath/jsonapi steps: - checkout diff --git a/.gitignore b/.gitignore index 19b1e1c..f468de4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /examples/examples + +.idea diff --git a/README.md b/README.md index 9656e88..2ca1e52 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A serializer/deserializer for JSON payloads that comply to the ## Installation ``` -go get -u github.com/moltin/jsonapi +go get -u github.com/elasticpath/jsonapi ``` Or, see [Alternative Installation](#alternative-installation). @@ -77,7 +77,7 @@ all of your data easily. ## Example App -[examples/app.go](https://github.com/moltin/jsonapi/blob/master/examples/app.go) +[examples/app.go](https://github.com/elasticpath/jsonapi/blob/master/examples/app.go) This program demonstrates the implementation of a create, a show, and a list [http.Handler](http://golang.org/pkg/net/http#Handler). It @@ -91,9 +91,9 @@ To run, * Make sure you have [Go installed](https://golang.org/doc/install) * Create the following directories or similar: `~/go` * Set `GOPATH` to `PWD` in your shell session, `export GOPATH=$PWD` -* `go get github.com/moltin/jsonapi`. (Append `-u` after `get` if you +* `go get github.com/elasticpath/jsonapi`. (Append `-u` after `get` if you are updating.) -* `cd $GOPATH/src/github.com/moltin/jsonapi/examples` +* `cd $GOPATH/src/github.com/elasticpath/jsonapi/examples` * `go build && ./examples` ## `jsonapi` Tag Reference @@ -198,7 +198,7 @@ about the rest? ### Create Record Example You can Unmarshal a JSON API payload using -[jsonapi.UnmarshalPayload](http://godoc.org/github.com/moltin/jsonapi#UnmarshalPayload). +[jsonapi.UnmarshalPayload](http://godoc.org/github.com/elasticpath/jsonapi#UnmarshalPayload). It reads from an [io.Reader](https://golang.org/pkg/io/#Reader) containing a JSON API payload for one record (but can have related records). Then, it materializes a struct that you created and passed in @@ -207,7 +207,7 @@ the top level, in request payloads at the moment. Bulk creates and updates are not supported yet. After saving your record, you can use, -[MarshalOnePayload](http://godoc.org/github.com/moltin/jsonapi#MarshalOnePayload), +[MarshalOnePayload](http://godoc.org/github.com/elasticpath/jsonapi#MarshalOnePayload), to write the JSON API response to an [io.Writer](https://golang.org/pkg/io/#Writer). @@ -217,7 +217,7 @@ to write the JSON API response to an UnmarshalPayload(in io.Reader, model interface{}) ``` -Visit [godoc](http://godoc.org/github.com/moltin/jsonapi#UnmarshalPayload) +Visit [godoc](http://godoc.org/github.com/elasticpath/jsonapi#UnmarshalPayload) #### `MarshalPayload` @@ -225,7 +225,7 @@ Visit [godoc](http://godoc.org/github.com/moltin/jsonapi#UnmarshalPayload) MarshalPayload(w io.Writer, models interface{}) error ``` -Visit [godoc](http://godoc.org/github.com/moltin/jsonapi#MarshalPayload) +Visit [godoc](http://godoc.org/github.com/elasticpath/jsonapi#MarshalPayload) Writes a JSON API response, with related records sideloaded, into an `included` array. This method encodes a response for either a single record or @@ -261,7 +261,7 @@ func CreateBlog(w http.ResponseWriter, r *http.Request) { UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) ``` -Visit [godoc](http://godoc.org/github.com/moltin/jsonapi#UnmarshalManyPayload) +Visit [godoc](http://godoc.org/github.com/elasticpath/jsonapi#UnmarshalManyPayload) Takes an `io.Reader` and a `reflect.Type` representing the uniform type contained within the `"data"` JSON API member. @@ -427,7 +427,7 @@ if err := validate(&myStructToValidate); err != nil { MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error ``` -Visit [godoc](http://godoc.org/github.com/moltin/jsonapi#MarshalOnePayloadEmbedded) +Visit [godoc](http://godoc.org/github.com/elasticpath/jsonapi#MarshalOnePayloadEmbedded) This method is not strictly meant to for use in implementation code, although feel free. It was mainly created for use in tests; in most cases, @@ -467,13 +467,13 @@ I use git subtrees to manage dependencies rather than `go get` so that the src is committed to my repo. ``` -git subtree add --squash --prefix=src/github.com/moltin/jsonapi https://github.com/moltin/jsonapi.git master +git subtree add --squash --prefix=src/github.com/elasticpath/jsonapi https://github.com/elasticpath/jsonapi.git master ``` To update, ``` -git subtree pull --squash --prefix=src/github.com/moltin/jsonapi https://github.com/moltin/jsonapi.git master +git subtree pull --squash --prefix=src/github.com/elasticpath/jsonapi https://github.com/elasticpath/jsonapi.git master ``` This assumes that I have my repo structured with a `src` dir containing diff --git a/constants.go b/constants.go index d035df4..d20d906 100644 --- a/constants.go +++ b/constants.go @@ -34,6 +34,9 @@ const ( // KeyNextPage is the key to the links object whose value contains a link to // the next page of data KeyNextPage = "next" + // KeyCurrentPage is the key to the links object whose value contains a link + // to the current page of data + KeyCurrentPage = "current" // QueryParamPageNumber is a JSON API query parameter used in a page based // pagination strategy in conjunction with QueryParamPageSize diff --git a/doc.go b/doc.go index 15db638..273ab04 100644 --- a/doc.go +++ b/doc.go @@ -65,6 +65,6 @@ the key in the "relationships" hash for the record. Use the methods below to Marshal and Unmarshal jsonapi.org json payloads. -Visit the readme at https://github.com/moltin/jsonapi +Visit the readme at https://github.com/elasticpath/jsonapi */ package jsonapi diff --git a/errors_test.go b/errors_test.go index 1bf7148..be33049 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,7 +8,7 @@ import ( "reflect" "testing" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) func TestErrorObjectWritesExpectedErrorMessage(t *testing.T) { diff --git a/examples/app.go b/examples/app.go index 8486266..65a134a 100644 --- a/examples/app.go +++ b/examples/app.go @@ -10,7 +10,7 @@ import ( "net/http/httptest" "time" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) func main() { diff --git a/examples/handler.go b/examples/handler.go index ee5bb62..8b0dfe0 100644 --- a/examples/handler.go +++ b/examples/handler.go @@ -4,7 +4,7 @@ import ( "net/http" "strconv" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) const ( diff --git a/examples/handler_test.go b/examples/handler_test.go index 6b293bc..152b00d 100644 --- a/examples/handler_test.go +++ b/examples/handler_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) func TestExampleHandler_post(t *testing.T) { diff --git a/examples/models.go b/examples/models.go index 012502e..d826181 100644 --- a/examples/models.go +++ b/examples/models.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) // Blog is a model representing a blog site diff --git a/go.mod b/go.mod index 94154a4..3d02216 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/moltin/jsonapi +module github.com/elasticpath/jsonapi go 1.16 diff --git a/models_test.go b/models_test.go index baf1514..e89dd80 100644 --- a/models_test.go +++ b/models_test.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) type BadModel struct { @@ -76,13 +76,14 @@ type Book struct { } type Blog struct { - ID int `jsonapi:"primary,blogs"` - Title string `jsonapi:"attr,title"` - Posts []*Post `jsonapi:"relation,posts"` - CurrentPost *Post `jsonapi:"relation,current_post"` - CurrentPostID int `jsonapi:"attr,current_post_id"` - CreatedAt time.Time `jsonapi:"attr,created_at"` - ViewCount int `jsonapi:"attr,view_count"` + ID int `jsonapi:"primary,blogs"` + Title string `jsonapi:"attr,title"` + Posts []*Post `jsonapi:"relation,posts"` + CurrentPost *Post `jsonapi:"relation,current_post"` + CurrentPostID int `jsonapi:"attr,current_post_id"` + CreatedAt time.Time `jsonapi:"attr,created_at"` + ViewCount int `jsonapi:"attr,view_count"` + Ratings [][]string `jsonapi:"attr,ratings"` } func (b *Blog) JSONAPILinks() *jsonapi.Links { diff --git a/primatives.go b/primatives.go index d7dd80c..86f8594 100644 --- a/primatives.go +++ b/primatives.go @@ -19,7 +19,7 @@ func (i *JSONInt) UnmarshalJSON(data []byte) error { // If this method was called, the value was set. i.Set = true - if data == nil { + if data == nil || string(data) == "null" { // The key was set to null i.Null = true return nil diff --git a/request.go b/request.go index 16063db..50b88bf 100644 --- a/request.go +++ b/request.go @@ -86,7 +86,7 @@ func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField refl // } // // -// Visit https://github.com/moltin/jsonapi#create for more info. +// Visit https://github.com/elasticpath/jsonapi#create for more info. // // model interface{} should be a pointer to a struct. func UnmarshalPayload(in io.Reader, model interface{}) error { @@ -734,6 +734,13 @@ func handleSlice( return reflect.Value{}, tErr } + // If this is a slice of slices, just append and move on + // TODO: This only accounts for strings, we need to account for every type in future + if v.Type() == reflect.TypeOf([]string{}) { + vals = reflect.Append(vals, v) + continue + } + if v.Kind() == reflect.Slice { vals = reflect.Append(vals, v.Elem()) } else { diff --git a/request_test.go b/request_test.go index ef7ea50..a35d27b 100644 --- a/request_test.go +++ b/request_test.go @@ -3,6 +3,7 @@ package jsonapi_test import ( "bytes" "encoding/json" + "fmt" "io" "reflect" "sort" @@ -10,7 +11,7 @@ import ( "testing" "time" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) func unmarshalSamplePayload() (*Blog, error) { @@ -171,6 +172,23 @@ func sampleWithPointerPayload(m map[string]interface{}) io.Reader { return out } +func samplePayloadWithSliceOfSlice() io.Reader { + payload := &jsonapi.OnePayload{ + Data: &jsonapi.ResourceObj{ + ID: "2", + Type: "blogs", + Attributes: map[string]interface{}{ + "ratings": [][]string{{"1", "2"}}, + }, + }, + } + + out := bytes.NewBuffer(nil) + json.NewEncoder(out).Encode(payload) + + return out +} + func testModel() *Blog { return &Blog{ ID: 5, @@ -549,6 +567,29 @@ func TestUnmarshalSetsID(t *testing.T) { } } +func TestUnmarshalSetsSliceOfSlices(t *testing.T) { + in := samplePayloadWithSliceOfSlice() + out := new(Blog) + + if err := jsonapi.UnmarshalPayload(in, out); err != nil { + t.Fatal(err) + } + + fmt.Printf("%+v", out) + + if len(out.Ratings) != 1 { + t.Fatal("Did not set ratings slice") + } + + if len(out.Ratings[0]) != 2 { + t.Fatal("Did not set ratings slice") + } + + if out.Ratings[0][0] != "1" { + t.Fatalf("Did not set ratings slice") + } +} + func TestUnmarshal_nonNumericID(t *testing.T) { data := samplePayloadWithoutIncluded() data["data"].(map[string]interface{})["id"] = "non-numeric-id" @@ -1297,8 +1338,8 @@ func TestUnmarshalNestedStructSlice(t *testing.T) { } } -func TestNumericTypes(t *testing.T) { - var tests = map[string]struct{ +func TestNumericTypes(t *testing.T) { + var tests = map[string]struct { In map[string]interface{} }{ "Int": { @@ -1324,7 +1365,6 @@ func TestNumericTypes(t *testing.T) { } type pLoad struct { - } out := new(Numeric) for name, test := range tests { @@ -1348,7 +1388,6 @@ func TestNumericTypes(t *testing.T) { } }) - } } diff --git a/resource.go b/resource.go index d888483..d68d83a 100644 --- a/resource.go +++ b/resource.go @@ -174,10 +174,6 @@ type OffsetPagination struct { } func (p *OffsetPagination) GeneratePagination() *Links { - if p.Total < p.Limit { // no pagination needed - return nil - } - // initiate the URL - if the page offset and Limit have not been set or is devoid of all // query parameters then initialising will make string replacement a simple operation @@ -195,22 +191,32 @@ func (p *OffsetPagination) GeneratePagination() *Links { } offset := int64(math.Max(float64(getPageParam("offset", p.URL)), float64(0))) - if offset > 0 { - firstUrl := p.URL - replaceParam(&firstUrl, `page[limit]`, strconv.FormatInt(limit, 10)) - replaceParam(&firstUrl, `page[offset]`, strconv.FormatInt(0, 10)) - links[KeyFirstPage] = firstUrl - } + // current link is always present + currentUrl := p.URL + replaceParam(¤tUrl, `page[limit]`, strconv.FormatInt(limit, 10)) + replaceParam(¤tUrl, `page[offset]`, strconv.FormatInt(offset, 10)) + links[KeyCurrentPage] = currentUrl - if offset > limit { + // first link is always present + firstUrl := p.URL + replaceParam(&firstUrl, `page[limit]`, strconv.FormatInt(limit, 10)) + replaceParam(&firstUrl, `page[offset]`, strconv.FormatInt(0, 10)) + links[KeyFirstPage] = firstUrl + + // prev link is present on all pages except the first + if offset > 0 { prevUrl := p.URL replaceParam(&prevUrl, `page[limit]`, strconv.FormatInt(limit, 10)) prevOffset := offset - limit + if prevOffset < 0 { + prevOffset = 0 + } replaceParam(&prevUrl, `page[offset]`, strconv.FormatInt(prevOffset, 10)) links[KeyPreviousPage] = prevUrl } - if offset+limit < p.Total-limit { + // next link is present on all pages except the last + if offset+limit < p.Total { nextUrl := p.URL replaceParam(&nextUrl, `page[limit]`, strconv.FormatInt(limit, 10)) nextOffset := offset + limit @@ -218,22 +224,24 @@ func (p *OffsetPagination) GeneratePagination() *Links { links[KeyNextPage] = nextUrl } - if offset+limit < p.Total { - lastUrl := p.URL - replaceParam(&lastUrl, `page[limit]`, strconv.FormatInt(limit, 10)) - pages := p.Total / limit - if p.Total%limit > 0 { - pages += 1 - } - lastOffset := ((pages - 1) * limit) - offsetShift := offset % limit - lastOffset += offsetShift - if lastOffset > p.Total { - lastOffset -= limit - } - replaceParam(&lastUrl, `page[offset]`, strconv.FormatInt(lastOffset, 10)) - links[KeyLastPage] = lastUrl + // last link is always present + lastUrl := p.URL + replaceParam(&lastUrl, `page[limit]`, strconv.FormatInt(limit, 10)) + pages := p.Total / limit + if p.Total%limit > 0 { + pages += 1 + } + lastOffset := ((pages - 1) * limit) + offsetShift := offset % limit + lastOffset += offsetShift + if lastOffset > p.Total { + lastOffset -= limit + } + if lastOffset < 0 { + lastOffset = 0 } + replaceParam(&lastUrl, `page[offset]`, strconv.FormatInt(lastOffset, 10)) + links[KeyLastPage] = lastUrl return &links } diff --git a/resource_test.go b/resource_test.go index 8b04539..765307a 100644 --- a/resource_test.go +++ b/resource_test.go @@ -18,8 +18,10 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ - KeyNextPage: "/?page[limit]=100&page[offset]=100", - KeyLastPage: "/?page[limit]=100&page[offset]=300", + KeyCurrentPage: "/?page[limit]=100&page[offset]=0", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=100", + KeyLastPage: "/?page[limit]=100&page[offset]=300", }, }, "0 offset and total a multiple of limit": { @@ -29,8 +31,10 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 300, }, result: Links{ - KeyNextPage: "/?page[limit]=100&page[offset]=100", - KeyLastPage: "/?page[limit]=100&page[offset]=200", + KeyCurrentPage: "/?page[limit]=100&page[offset]=0", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=100", + KeyLastPage: "/?page[limit]=100&page[offset]=200", }, }, "Offset below limit": { @@ -40,9 +44,11 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ - KeyFirstPage: "/?page[limit]=100&page[offset]=0", - KeyNextPage: "/?page[limit]=100&page[offset]=180", - KeyLastPage: "/?page[limit]=100&page[offset]=280", + KeyCurrentPage: "/?page[limit]=100&page[offset]=80", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyPreviousPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=180", + KeyLastPage: "/?page[limit]=100&page[offset]=280", }, }, "Mid range offset": { @@ -52,6 +58,7 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=111", KeyFirstPage: "/?page[limit]=100&page[offset]=0", KeyPreviousPage: "/?page[limit]=100&page[offset]=11", KeyNextPage: "/?page[limit]=100&page[offset]=211", @@ -65,6 +72,7 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=111&page[sort]=-1&aparam=2", KeyFirstPage: "/?page[limit]=100&page[offset]=0&page[sort]=-1&aparam=2", KeyPreviousPage: "/?page[limit]=100&page[offset]=11&page[sort]=-1&aparam=2", KeyNextPage: "/?page[limit]=100&page[offset]=211&page[sort]=-1&aparam=2", @@ -78,6 +86,7 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ + KeyCurrentPage: "/?page[sort]=-1&aparam=2&page[limit]=100&page[offset]=111", KeyFirstPage: "/?page[sort]=-1&aparam=2&page[limit]=100&page[offset]=0", KeyPreviousPage: "/?page[sort]=-1&aparam=2&page[limit]=100&page[offset]=11", KeyNextPage: "/?page[sort]=-1&aparam=2&page[limit]=100&page[offset]=211", @@ -91,6 +100,7 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ + KeyCurrentPage: "/?page[sort]=-1&page[limit]=100&aparam=2&page[offset]=111&lastparam=owt", KeyFirstPage: "/?page[sort]=-1&page[limit]=100&aparam=2&page[offset]=0&lastparam=owt", KeyPreviousPage: "/?page[sort]=-1&page[limit]=100&aparam=2&page[offset]=11&lastparam=owt", KeyNextPage: "/?page[sort]=-1&page[limit]=100&aparam=2&page[offset]=211&lastparam=owt", @@ -104,8 +114,10 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ - KeyNextPage: "/?page[limit]=100&page[offset]=100", - KeyLastPage: "/?page[limit]=100&page[offset]=300", + KeyCurrentPage: "/?page[limit]=100&page[offset]=0", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=100", + KeyLastPage: "/?page[limit]=100&page[offset]=300", }, }, "No paging set": { @@ -115,8 +127,10 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ - KeyNextPage: "/?param=owt&page[limit]=100&page[offset]=100", - KeyLastPage: "/?param=owt&page[limit]=100&page[offset]=300", + KeyCurrentPage: "/?param=owt&page[limit]=100&page[offset]=0", + KeyFirstPage: "/?param=owt&page[limit]=100&page[offset]=0", + KeyNextPage: "/?param=owt&page[limit]=100&page[offset]=100", + KeyLastPage: "/?param=owt&page[limit]=100&page[offset]=300", }, }, "Non numeric parameter values": { @@ -126,8 +140,63 @@ func TestOffsetPagination_GeneratePagination(t *testing.T) { Total: 334, }, result: Links{ - KeyLastPage: "/?page[limit]=100&page[offset]=300", - KeyNextPage: "/?page[limit]=100&page[offset]=100", + KeyCurrentPage: "/?page[limit]=100&page[offset]=0", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=100", + KeyLastPage: "/?page[limit]=100&page[offset]=300", + }, + }, + "Single page - total less than limit": { + pagination: OffsetPagination{ + URL: "/?page[limit]=100&page[offset]=0", + Limit: 100, + Total: 50, + }, + result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=0", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyLastPage: "/?page[limit]=100&page[offset]=0", + }, + }, + "Last page - no next link": { + pagination: OffsetPagination{ + URL: "/?page[limit]=100&page[offset]=300", + Limit: 100, + Total: 334, + }, + result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=300", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyPreviousPage: "/?page[limit]=100&page[offset]=200", + KeyLastPage: "/?page[limit]=100&page[offset]=300", + }, + }, + "Second page - prev link present": { + pagination: OffsetPagination{ + URL: "/?page[limit]=100&page[offset]=100", + Limit: 100, + Total: 334, + }, + result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=100", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyPreviousPage: "/?page[limit]=100&page[offset]=0", + KeyNextPage: "/?page[limit]=100&page[offset]=200", + KeyLastPage: "/?page[limit]=100&page[offset]=300", + }, + }, + "Second to last page - next link present": { + pagination: OffsetPagination{ + URL: "/?page[limit]=100&page[offset]=200", + Limit: 100, + Total: 334, + }, + result: Links{ + KeyCurrentPage: "/?page[limit]=100&page[offset]=200", + KeyFirstPage: "/?page[limit]=100&page[offset]=0", + KeyPreviousPage: "/?page[limit]=100&page[offset]=100", + KeyNextPage: "/?page[limit]=100&page[offset]=300", + KeyLastPage: "/?page[limit]=100&page[offset]=300", }, }, } @@ -155,6 +224,11 @@ func TestManyPayload_AddPagination(t *testing.T) { }, expected: ManyPayload{ Data: nil, + Links: &Links{ + KeyCurrentPage: "?page[limit]=100&page[offset]=0", + KeyFirstPage: "?page[limit]=100&page[offset]=0", + KeyLastPage: "?page[limit]=100&page[offset]=0", + }, Meta: &Meta{ "results": &Meta{ "total": int64(10), @@ -175,6 +249,11 @@ func TestManyPayload_AddPagination(t *testing.T) { }, expected: ManyPayload{ Data: nil, + Links: &Links{ + KeyCurrentPage: "?page[limit]=100&page[offset]=0", + KeyFirstPage: "?page[limit]=100&page[offset]=0", + KeyLastPage: "?page[limit]=100&page[offset]=0", + }, Meta: &Meta{ "foo": "bar", "results": &Meta{ diff --git a/response.go b/response.go index 9eefa43..fae6acd 100644 --- a/response.go +++ b/response.go @@ -408,8 +408,14 @@ func visitModelNode(model interface{}, included *map[string]*ResourceObj, } // See if we need to omit this field - if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) { - continue + isSlice := fieldValue.Type().Kind() == reflect.Slice + if omitEmpty { + if isSlice && fieldValue.Len() < 1 || (!isSlice && fieldValue.IsNil()) { + continue + } + if reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) { + continue + } } strAttr, ok := fieldValue.Interface().(string) @@ -534,6 +540,11 @@ func visitModelNode(model interface{}, included *map[string]*ResourceObj, node.Meta = metableModel.JSONAPIMeta() } + // Don't return empty meta + if node.Meta != nil && len(*node.Meta) == 0 { + node.Meta = nil + } + return node, nil } diff --git a/response_test.go b/response_test.go index c83f193..4424ca1 100644 --- a/response_test.go +++ b/response_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/moltin/jsonapi" + "github.com/elasticpath/jsonapi" ) func TestMarshalPayload(t *testing.T) { @@ -331,7 +331,7 @@ func TestMarshalOnePayload_omitIDString(t *testing.T) { payload := jsonData["data"].(map[string]interface{}) // Verify that empty ID of type string gets omitted. See: - // https://github.com/moltin/jsonapi/issues/83#issuecomment-285611425 + // https://github.com/elasticpath/jsonapi/issues/83#issuecomment-285611425 _, ok := payload["id"] if ok { t.Fatal("Was expecting the data.id member to be omitted")