Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.

Commit 40560af

Browse files
committed
Used to have 6 functions: MarshalOnePayload, MarshalOnePayloadWithoutIncluded, MarshalOne, MarshalManyPayload, MarshalManyPayloadWithoutIncluded, MarshalMany - now there are only 3: MarshalPayload, MarshalPayloadWithoutIncluded, Marshal. This simplifies the public API; will be easier to explain and document while still providing the existing support for struct ptrs (&Blog{}) and slices of struct ptrs ([]*Blog{}).
1 parent af3dab1 commit 40560af

File tree

6 files changed

+160
-147
lines changed

6 files changed

+160
-147
lines changed

examples/handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (h *ExampleHandler) createBlog(w http.ResponseWriter, r *http.Request) {
5656
w.WriteHeader(http.StatusCreated)
5757
w.Header().Set(headerContentType, jsonapi.MediaType)
5858

59-
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
59+
if err := jsonapiRuntime.MarshalPayload(w, blog); err != nil {
6060
http.Error(w, err.Error(), http.StatusInternalServerError)
6161
}
6262
}
@@ -93,7 +93,7 @@ func (h *ExampleHandler) showBlog(w http.ResponseWriter, r *http.Request) {
9393
w.WriteHeader(http.StatusOK)
9494

9595
w.Header().Set(headerContentType, jsonapi.MediaType)
96-
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
96+
if err := jsonapiRuntime.MarshalPayload(w, blog); err != nil {
9797
http.Error(w, err.Error(), http.StatusInternalServerError)
9898
}
9999
}
@@ -108,7 +108,7 @@ func (h *ExampleHandler) listBlogs(w http.ResponseWriter, r *http.Request) {
108108
w.Header().Set("Content-Type", jsonapi.MediaType)
109109
w.WriteHeader(http.StatusOK)
110110

111-
if err := jsonapiRuntime.MarshalManyPayload(w, blogs); err != nil {
111+
if err := jsonapiRuntime.MarshalPayload(w, blogs); err != nil {
112112
http.Error(w, err.Error(), http.StatusInternalServerError)
113113
}
114114
}

node.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ package jsonapi
22

33
import "fmt"
44

5+
// Payloader is used to encapsulate the One and Many payload types
6+
type Payloader interface {
7+
clearIncluded()
8+
}
9+
510
// OnePayload is used to represent a generic JSON API payload where a single
611
// resource (Node) was included as an {} in the "data" key
712
type OnePayload struct {
@@ -11,6 +16,10 @@ type OnePayload struct {
1116
Meta *Meta `json:"meta,omitempty"`
1217
}
1318

19+
func (p *OnePayload) clearIncluded() {
20+
p.Included = []*Node{}
21+
}
22+
1423
// ManyPayload is used to represent a generic JSON API payload where many
1524
// resources (Nodes) were included in an [] in the "data" key
1625
type ManyPayload struct {
@@ -20,6 +29,10 @@ type ManyPayload struct {
2029
Meta *Meta `json:"meta,omitempty"`
2130
}
2231

32+
func (p *ManyPayload) clearIncluded() {
33+
p.Included = []*Node{}
34+
}
35+
2336
// Node is used to represent a generic JSON API Resource
2437
type Node struct {
2538
Type string `json:"type"`

request_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ func samplePayloadWithSideloaded() io.Reader {
931931
testModel := testModel()
932932

933933
out := bytes.NewBuffer(nil)
934-
MarshalOnePayload(out, testModel)
934+
MarshalPayload(out, testModel)
935935

936936
return out
937937
}

response.go

Lines changed: 83 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -23,53 +23,103 @@ var (
2323
// be a slice of *Structs; MarshalMany will return this error when its
2424
// interface{} argument is invalid.
2525
ErrExpectedSlice = errors.New("models should be a slice of struct pointers")
26+
// ErrUnexpectedType is returned when marshalling an interface; the interface
27+
// had to be a pointer or a slice; otherwise this error is returned.
28+
ErrUnexpectedType = errors.New("models should be a struct pointer or slice of struct pointers")
2629
)
2730

28-
// MarshalOnePayload writes a jsonapi response with one, with related records
29-
// sideloaded, into "included" array. This method encodes a response for a
30-
// single record only. Hence, data will be a single record rather than an array
31-
// of records. If you want to serialize many records, see, MarshalManyPayload.
31+
// MarshalPayload writes a jsonapi response for one or many records. The
32+
// related records are sideloaded into the "included" array. If this method is
33+
// given a struct pointer as an argument it will serialize in the form
34+
// "data": {...}. If this method is given a slice of pointers, this method will
35+
// serialize in the form "data": [...]
3236
//
33-
// See UnmarshalPayload for usage example.
37+
// One Example: you could pass it, w, your http.ResponseWriter, and, models, a
38+
// ptr to a Blog to be written to the response body:
3439
//
35-
// model interface{} should be a pointer to a struct.
36-
func MarshalOnePayload(w io.Writer, model interface{}) error {
37-
payload, err := MarshalOne(model)
40+
// func ShowBlog(w http.ResponseWriter, r *http.Request) {
41+
// blog := &Blog{}
42+
//
43+
// w.Header().Set("Content-Type", jsonapi.MediaType)
44+
// w.WriteHeader(http.StatusOK)
45+
//
46+
// if err := jsonapi.MarshalPayload(w, blog); err != nil {
47+
// http.Error(w, err.Error(), http.StatusInternalServerError)
48+
// }
49+
// }
50+
//
51+
// Many Example: you could pass it, w, your http.ResponseWriter, and, models, a
52+
// slice of Blog struct instance pointers to be written to the response body:
53+
//
54+
// func ListBlogs(w http.ResponseWriter, r *http.Request) {
55+
// blogs := []*Blog{}
56+
//
57+
// w.Header().Set("Content-Type", jsonapi.MediaType)
58+
// w.WriteHeader(http.StatusOK)
59+
//
60+
// if err := jsonapi.MarshalPayload(w, blogs); err != nil {
61+
// http.Error(w, err.Error(), http.StatusInternalServerError)
62+
// }
63+
// }
64+
//
65+
func MarshalPayload(w io.Writer, models interface{}) error {
66+
payload, err := Marshal(models)
3867
if err != nil {
3968
return err
4069
}
4170

4271
if err := json.NewEncoder(w).Encode(payload); err != nil {
4372
return err
4473
}
45-
4674
return nil
4775
}
4876

49-
// MarshalOnePayloadWithoutIncluded writes a jsonapi response with one object,
50-
// without the related records sideloaded into "included" array. If you want to
51-
// serialize the relations into the "included" array see MarshalOnePayload.
52-
//
53-
// model interface{} should be a pointer to a struct.
54-
func MarshalOnePayloadWithoutIncluded(w io.Writer, model interface{}) error {
55-
included := make(map[string]*Node)
77+
// Marshal does the same as MarshalPayload except it just returns the payload
78+
// and doesn't write out results. Useful if you use your own JSON rendering
79+
// library.
80+
func Marshal(models interface{}) (Payloader, error) {
81+
switch vals := reflect.ValueOf(models); vals.Kind() {
82+
case reflect.Slice:
83+
m, err := convertToSliceInterface(&models)
84+
if err != nil {
85+
return nil, err
86+
}
87+
return marshalMany(m)
88+
case reflect.Ptr:
89+
// Check that the pointer was to a struct
90+
if reflect.Indirect(vals).Kind() != reflect.Struct {
91+
return nil, ErrUnexpectedType
92+
}
93+
return marshalOne(models)
94+
default:
95+
return nil, ErrUnexpectedType
96+
}
97+
}
5698

57-
rootNode, err := visitModelNode(model, &included, true)
99+
// MarshalPayloadWithoutIncluded writes a jsonapi response with one or many
100+
// records, without the related records sideloaded into "included" array.
101+
// If you want to serialize the relations into the "included" array see
102+
// MarshalPayload.
103+
//
104+
// models interface{} should be either a struct pointer or a slice of struct
105+
// pointers.
106+
func MarshalPayloadWithoutIncluded(w io.Writer, model interface{}) error {
107+
payload, err := Marshal(model)
58108
if err != nil {
59109
return err
60110
}
111+
payload.clearIncluded()
61112

62-
if err := json.NewEncoder(w).Encode(&OnePayload{Data: rootNode}); err != nil {
113+
if err := json.NewEncoder(w).Encode(payload); err != nil {
63114
return err
64115
}
65-
66116
return nil
67117
}
68118

69-
// MarshalOne does the same as MarshalOnePayload except it just returns the
119+
// marshalOne does the same as MarshalOnePayload except it just returns the
70120
// payload and doesn't write out results. Useful is you use your JSON rendering
71121
// library.
72-
func MarshalOne(model interface{}) (*OnePayload, error) {
122+
func marshalOne(model interface{}) (*OnePayload, error) {
73123
included := make(map[string]*Node)
74124

75125
rootNode, err := visitModelNode(model, &included, true)
@@ -83,78 +133,10 @@ func MarshalOne(model interface{}) (*OnePayload, error) {
83133
return payload, nil
84134
}
85135

86-
// MarshalManyPayloadWithoutIncluded writes a jsonapi response with many records,
87-
// without the related records sideloaded into "included" array. If you want to
88-
// serialize the relations into the "included" array see MarshalManyPayload.
89-
//
90-
// models interface{} should be a slice of struct pointers.
91-
func MarshalManyPayloadWithoutIncluded(w io.Writer, models interface{}) error {
92-
m, err := convertToSliceInterface(&models)
93-
if err != nil {
94-
return err
95-
}
96-
payload, err := MarshalMany(m)
97-
if err != nil {
98-
return err
99-
}
100-
101-
// Empty the included
102-
payload.Included = []*Node{}
103-
104-
if err := json.NewEncoder(w).Encode(payload); err != nil {
105-
return err
106-
}
107-
108-
return nil
109-
}
110-
111-
// MarshalManyPayload writes a jsonapi response with many records, with related
112-
// records sideloaded, into "included" array. This method encodes a response for
113-
// a slice of records, hence data will be an array of records rather than a
114-
// single record. To serialize a single record, see MarshalOnePayload
115-
//
116-
// For example you could pass it, w, your http.ResponseWriter, and, models, a
117-
// slice of Blog struct instance pointers as interface{}'s to write to the
118-
// response,
119-
//
120-
// func ListBlogs(w http.ResponseWriter, r *http.Request) {
121-
// // ... fetch your blogs and filter, offset, limit, etc ...
122-
//
123-
// blogs := testBlogsForList()
124-
//
125-
// w.Header().Set("Content-Type", jsonapi.MediaType)
126-
// w.WriteHeader(http.StatusOK)
127-
//
128-
// if err := jsonapi.MarshalManyPayload(w, blogs); err != nil {
129-
// http.Error(w, err.Error(), http.StatusInternalServerError)
130-
// }
131-
// }
132-
//
133-
//
134-
// Visit https://github.com/google/jsonapi#list for more info.
135-
//
136-
// models interface{} should be a slice of struct pointers.
137-
func MarshalManyPayload(w io.Writer, models interface{}) error {
138-
m, err := convertToSliceInterface(&models)
139-
if err != nil {
140-
return err
141-
}
142-
payload, err := MarshalMany(m)
143-
if err != nil {
144-
return err
145-
}
146-
147-
if err := json.NewEncoder(w).Encode(payload); err != nil {
148-
return err
149-
}
150-
151-
return nil
152-
}
153-
154-
// MarshalMany does the same as MarshalManyPayload except it just returns the
136+
// marshalMany does the same as MarshalManyPayload except it just returns the
155137
// payload and doesn't write out results. Useful is you use your JSON rendering
156138
// library.
157-
func MarshalMany(models []interface{}) (*ManyPayload, error) {
139+
func marshalMany(models []interface{}) (*ManyPayload, error) {
158140
payload := &ManyPayload{
159141
Data: []*Node{},
160142
}
@@ -173,16 +155,18 @@ func MarshalMany(models []interface{}) (*ManyPayload, error) {
173155
}
174156

175157
// MarshalOnePayloadEmbedded - This method not meant to for use in
176-
// implementation code, although feel free. The purpose of this method is for
177-
// use in tests. In most cases, your request payloads for create will be
178-
// embedded rather than sideloaded for related records. This method will
179-
// serialize a single struct pointer into an embedded json response. In other
180-
// words, there will be no, "included", array in the json all relationships will
158+
// implementation code, although feel free. The purpose of this
159+
// method is for use in tests. In most cases, your request
160+
// payloads for create will be embedded rather than sideloaded for
161+
// related records. This method will serialize a single struct
162+
// pointer into an embedded json response. In other words, there
163+
// will be no, "included", array in the json all relationships will
181164
// be serailized inline in the data.
182165
//
183-
// However, in tests, you may want to construct payloads to post to create
184-
// methods that are embedded to most closely resemble the payloads that will be
185-
// produced by the client. This is what this method is intended for.
166+
// However, in tests, you may want to construct payloads to post
167+
// to create methods that are embedded to most closely resemble
168+
// the payloads that will be produced by the client. This is what
169+
// this method is intended for.
186170
//
187171
// model interface{} should be a pointer to a struct.
188172
func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error {

0 commit comments

Comments
 (0)