Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

Tips & Tricks

alex edited this page Jun 9, 2013 · 1 revision

Custom types

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"
}
Clone this wiki locally