-
Notifications
You must be signed in to change notification settings - Fork 56
Tips & Tricks
You can define your own types and use them directly as a field type in a service method request/response as long as they implement json.Marshaler
and json.Unmarshaler
interfaces.
Let's say we have this method:
func (s *MyService) ListItems(r *http.Request, req *ListReq, resp *ItemsList) error {
// fetch a list of items
}
where ListReq
and ItemsList
are defined as follows:
type ListReq struct {
Limit int `json:"limit,string" endpoints:"d=10,max=100"`
Page *QueryMarker `json:"cursor"`
}
type ItemsList struct {
Items []*Item `json:"items"`
Next *QueryMarker `json:"next,omitempty"`
}
What's interesting here is ListReq.Cursor
and ItemsList.Next
fields which are of type QueryMarker
:
import "appengine/datastore"
type QueryMarker struct {
datastore.Cursor
}
func (qm *QueryMarker) MarshalJSON() ([]byte, error) {
return []byte(`"` + qm.String() + `"`), nil
}
func (qm *QueryMarker) UnmarshalJSON(buf []byte) error {
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
return errors.New("QueryMarker: bad cursor value")
}
cursor, err := datastore.DecodeCursor(string(buf[1 : len(buf)-1]))
if err != nil {
return err
}
*qm = QueryMarker{cursor}
return nil
}
Now that our QueryMarker
implements required interfaces we can use ListReq.Page
field as if it were a datastore.Cursor
in our service method, for instance:
func (s *MyService) ListItems(r *http.Request, req *ListReq, list *ItemsList) error {
c := endpoints.NewContext(r)
list.Items = make([]*Item, 0, req.Limit)
q := datastore.NewQuery("Item").Limit(req.Limit)
if req.Page != nil {
q = q.Start(req.Page.Cursor)
}
var iter *datastore.Iterator
for iter := q.Run(c); ; {
var item Item
key, err := iter.Next(&item)
if err == datastore.Done {
break
}
if err != nil {
return err
}
item.Key = key
list.Items = append(list.Items, &item)
}
cur, err := iter.Cursor()
if err != nil {
return err
}
list.Next = &QueryMarker{cur}
return nil
}
A serialized ItemsList
would then look something like this:
{
"items": [
{
"id": "5629499534213120",
"name": "A TV set",
"price": 123.45
}
],
"next": "E-ABAIICImoNZGV2fmdvcGhtYXJrc3IRCxIEVXNlchiAgICAgICACgwU"
}
Another nice thing about this is, some types in appengine/datastore
package already implement json.Marshal and json.Unmarshal. Take, for instance, datastore.Key. I could use it as an ID in my JSON response out of the box, if I wanted to:
type User struct {
Key *datastore.Key `json:"id" datastore:"-"`
Name string `json:"name" datastore:"name"`
Role string `json:"role" datastore:"role"`
Email string `json:"email" datastore:"email"`
}
type GetUserReq struct {
Key *datastore.Key `json:"id"`
}
// defined with "users/{id}" path template
func (s *MyService) GetUser(r *http.Request, req *GetUserReq, user *User) error {
c := endpoints.NewContext(r)
if err := datastore.Get(c, req.Key, user); err != nil {
return err
}
user.Key = req.Key
return nil
}
JSON would then look something like this:
GET /_ah/api/myapi/v1/users/ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA
{
"id": "ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA",
"name": "John Doe",
"role": "member",
"email": "user@example.org"
}