Git Product home page Git Product logo

jsonapi's Introduction

jsonapi

Build Status Go Report Card GoDoc No Maintenance Intended

A serializer/deserializer for JSON payloads that comply to the JSON API - jsonapi.org spec in go.

Installation

go get -u github.com/google/jsonapi

Or, see Alternative Installation.

Background

You are working in your Go web application and you have a struct that is organized similarly to your database schema. You need to send and receive json payloads that adhere to the JSON API spec. Once you realize that your json needed to take on this special form, you go down the path of creating more structs to be able to serialize and deserialize JSON API payloads. Then there are more models required with this additional structure. Ugh! With JSON API, you can keep your model structs as is and use StructTags to indicate to JSON API how you want your response built or your request deserialized. What about your relationships? JSON API supports relationships out of the box and will even put them in your response into an included side-loaded slice--that contains associated records.

Introduction

JSON API uses StructField tags to annotate the structs fields that you already have and use in your app and then reads and writes JSON API output based on the instructions you give the library in your JSON API tags. Let's take an example. In your app, you most likely have structs that look similar to these:

type Blog struct {
	ID            int       `json:"id"`
	Title         string    `json:"title"`
	Posts         []*Post   `json:"posts"`
	CurrentPost   *Post     `json:"current_post"`
	CurrentPostId int       `json:"current_post_id"`
	CreatedAt     time.Time `json:"created_at"`
	ViewCount     int       `json:"view_count"`
}

type Post struct {
	ID       int        `json:"id"`
	BlogID   int        `json:"blog_id"`
	Title    string     `json:"title"`
	Body     string     `json:"body"`
	Comments []*Comment `json:"comments"`
}

type Comment struct {
	Id     int    `json:"id"`
	PostID int    `json:"post_id"`
	Body   string `json:"body"`
	Likes  uint   `json:"likes_count,omitempty"`
}

These structs may or may not resemble the layout of your database. But these are the ones that you want to use right? You wouldn't want to use structs like those that JSON API sends because it is difficult to get at all of your data easily.

Example App

examples/app.go

This program demonstrates the implementation of a create, a show, and a list http.Handler. It outputs some example requests and responses as well as serialized examples of the source/target structs to json. That is to say, I show you that the library has successfully taken your JSON API request and turned it into your struct types.

To run,

  • Make sure you have Go installed
  • Create the following directories or similar: ~/go
  • Set GOPATH to PWD in your shell session, export GOPATH=$PWD
  • go get github.com/google/jsonapi. (Append -u after get if you are updating.)
  • cd $GOPATH/src/github.com/google/jsonapi/examples
  • go build && ./examples

jsonapi Tag Reference

Example

The jsonapi StructTags tells this library how to marshal and unmarshal your structs into JSON API payloads and your JSON API payloads to structs, respectively. Then Use JSON API's Marshal and Unmarshal methods to construct and read your responses and replies. Here's an example of the structs above using JSON API tags:

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"`
}

type Post struct {
	ID       int        `jsonapi:"primary,posts"`
	BlogID   int        `jsonapi:"attr,blog_id"`
	Title    string     `jsonapi:"attr,title"`
	Body     string     `jsonapi:"attr,body"`
	Comments []*Comment `jsonapi:"relation,comments"`
}

type Comment struct {
	ID     int    `jsonapi:"primary,comments"`
	PostID int    `jsonapi:"attr,post_id"`
	Body   string `jsonapi:"attr,body"`
	Likes  uint   `jsonapi:"attr,likes-count,omitempty"`
}

Permitted Tag Values

primary

`jsonapi:"primary,<type field output>"`

This indicates this is the primary key field for this struct type. Tag value arguments are comma separated. The first argument must be, primary, and the second must be the name that should appear in the type* field for all data objects that represent this type of model.

* According the JSON API spec, the plural record types are shown in the examples, but not required.

attr

`jsonapi:"attr,<key name in attributes hash>,<optional: omitempty>"`

These fields' values will end up in the attributeshash for a record. The first argument must be, attr, and the second should be the name for the key to display in the attributes hash for that record. The optional third argument is omitempty - if it is present the field will not be present in the "attributes" if the field's value is equivalent to the field types empty value (ie if the count field is of type int, omitempty will omit the field when count has a value of 0). Lastly, the spec indicates that attributes key names should be dasherized for multiple word field names.

relation

`jsonapi:"relation,<key name in relationships hash>,<optional: omitempty>"`

Relations are struct fields that represent a one-to-one or one-to-many relationship with other structs. JSON API will traverse the graph of relationships and marshal or unmarshal records. The first argument must be, relation, and the second should be the name of the relationship, used as the key in the relationships hash for the record. The optional third argument is omitempty - if present will prevent non existent to-one and to-many from being serialized.

Methods Reference

All Marshal and Unmarshal methods expect pointers to struct instance or slices of the same contained with the interface{}s

Now you have your structs prepared to be serialized or materialized, What about the rest?

Create Record Example

You can Unmarshal a JSON API payload using jsonapi.UnmarshalPayload. It reads from an 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 (using new or &). Again, the method supports single records only, at 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, to write the JSON API response to an io.Writer.

UnmarshalPayload

UnmarshalPayload(in io.Reader, model interface{})

Visit godoc

MarshalPayload

MarshalPayload(w io.Writer, models interface{}) error

Visit godoc

Writes a JSON API response, with related records sideloaded, into an included array. This method encodes a response for either a single record or many records.

Handler Example Code
func CreateBlog(w http.ResponseWriter, r *http.Request) {
	blog := new(Blog)

	if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// ...save your blog...

	w.Header().Set("Content-Type", jsonapi.MediaType)
	w.WriteHeader(http.StatusCreated)

	if err := jsonapi.MarshalPayload(w, blog); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Create Records Example

UnmarshalManyPayload

UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)

Visit godoc

Takes an io.Reader and a reflect.Type representing the uniform type contained within the "data" JSON API member.

Handler Example Code
func CreateBlogs(w http.ResponseWriter, r *http.Request) {
	// ...create many blogs at once

	blogs, err := UnmarshalManyPayload(r.Body, reflect.TypeOf(new(Blog)))
	if err != nil {
		t.Fatal(err)
	}

	for _, blog := range blogs {
		b, ok := blog.(*Blog)
		// ...save each of your blogs
	}

	w.Header().Set("Content-Type", jsonapi.MediaType)
	w.WriteHeader(http.StatusCreated)

	if err := jsonapi.MarshalPayload(w, blogs); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Links

If you need to include link objects along with response data, implement the Linkable interface for document-links, and RelationshipLinkable for relationship links:

func (post Post) JSONAPILinks() *Links {
	return &Links{
		"self": "href": fmt.Sprintf("https://example.com/posts/%d", post.ID),
		"comments": Link{
			Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", post.ID),
			Meta: map[string]interface{}{
				"counts": map[string]uint{
					"likes":    4,
				},
			},
		},
	}
}

// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipLinks(relation string) *Links {
	if relation == "comments" {
		return &Links{
			"related": fmt.Sprintf("https://example.com/posts/%d/comments", post.ID),
		}
	}
	return nil
}

Meta

If you need to include meta objects along with response data, implement the Metable interface for document-meta, and RelationshipMetable for relationship meta:

func (post Post) JSONAPIMeta() *Meta {
   return &Meta{
   	"details": "sample details here",
   }
}

// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
   if relation == "comments" {
   	return &Meta{
   		"this": map[string]interface{}{
   			"can": map[string]interface{}{
   				"go": []interface{}{
   					"as",
   					"deep",
   					map[string]interface{}{
   						"as": "required",
   					},
   				},
   			},
   		},
   	}
   }
   return nil
}

Custom types

Custom types are supported for primitive types, only, as attributes. Examples,

type CustomIntType int
type CustomFloatType float64
type CustomStringType string

Types like following are not supported, but may be in the future:

type CustomMapType map[string]interface{}
type CustomSliceMapType []map[string]interface{}

Errors

This package also implements support for JSON API compatible errors payloads using the following types.

MarshalErrors

MarshalErrors(w io.Writer, errs []*ErrorObject) error

Writes a JSON API response using the given []error.

ErrorsPayload

type ErrorsPayload struct {
	Errors []*ErrorObject `json:"errors"`
}

ErrorsPayload is a serializer struct for representing a valid JSON API errors payload.

ErrorObject

type ErrorObject struct { ... }

// Error implements the `Error` interface.
func (e *ErrorObject) Error() string {
	return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail)
}

ErrorObject is an Error implementation as well as an implementation of the JSON API error object.

The main idea behind this struct is that you can use it directly in your code as an error type and pass it directly to MarshalErrors to get a valid JSON API errors payload.

Errors Example Code
// An error has come up in your code, so set an appropriate status, and serialize the error.
if err := validate(&myStructToValidate); err != nil {
	context.SetStatusCode(http.StatusBadRequest) // Or however you need to set a status.
	jsonapi.MarshalErrors(w, []*ErrorObject{{
		Title: "Validation Error",
		Detail: "Given request body was invalid.",
		Status: "400",
		Meta: map[string]interface{}{"field": "some_field", "error": "bad type", "expected": "string", "received": "float64"},
	}})
	return
}

Testing

MarshalOnePayloadEmbedded

MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error

Visit godoc

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, your request payloads for create will be embedded rather than sideloaded for related records. This method will serialize a single struct pointer into an embedded json response. In other words, there will be no, included, array in the json; all relationships will be serialized inline with the data.

However, in tests, you may want to construct payloads to post to create methods that are embedded to most closely model the payloads that will be produced by the client. This method aims to enable that.

Example

out := bytes.NewBuffer(nil)

// testModel returns a pointer to a Blog
jsonapi.MarshalOnePayloadEmbedded(out, testModel())

h := new(BlogsHandler)

w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/blogs", out)

h.CreateBlog(w, r)

blog := new(Blog)
jsonapi.UnmarshalPayload(w.Body, blog)

// ... assert stuff about blog here ...

Alternative Installation

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/google/jsonapi https://github.com/google/jsonapi.git master

To update,

git subtree pull --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master

This assumes that I have my repo structured with a src dir containing a collection of packages and GOPATH is set to the root folder--containing src.

Contributing

Fork, Change, Pull Request with tests.

jsonapi's People

Contributors

angelospanag avatar aren55555 avatar bored-engineer avatar brandonbloom avatar christianklotz avatar dennisfaust avatar dustinsmith1024 avatar ejilay avatar geoffgarside avatar hypnoglow avatar jasonmccallister avatar keighl avatar keijiyoshida avatar lulezi avatar mattbostock avatar mirekp avatar morenoh149 avatar mrowdy avatar msabramo avatar nstratos avatar omarismail avatar quetzyg avatar sbelkin88 avatar sharonjl avatar shwoodard avatar sjauld avatar swr avatar thedodd avatar ujjwalsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsonapi's Issues

As documented, links don't work correctly

As documented, unfortunately links don't work. This is how it's documented:

func (post Post) JSONAPILinks() *map[string]interface{} {
    return &map[string]interface{}{
        "self": "href": fmt.Sprintf("https://example.com/posts/%d", post.ID),
    }
}

This is how it works:

func (post Post) JSONAPILinks() *jsonapi.Links {
	return &jsonapi.Links{
                "self": fmt.Sprintf("https://example.com/posts/%d", post.ID),
	}
}

I'm actually not clear from the pull on the intention now that I re-read it. I can send a fix in either case.

Type validation is needed.

Currently, if a struct field like:

Name string `jsonapi:"attr,name"`

is being used, and a user sends in a payload with an invalid type for that field, like:

{"data": {"type": "whatevers", "attributes": {"name": 1}}}

this library simply returns an error looking like: "data is not a jsonapi representation of '*mypkg.Whatever'", which is not useful.

We need to get some type validation in place. I've already started hacking on the code to get this working. Hopefully will have a PR open soon.

Links and Meta on root level

Hi, i've this struct

type User struct {
	ID      int    `jsonapi:"primary,users"`
	Code    string `jsonapi:"attr,code"`
	Name    string `jsonapi:"attr,name"`
	Surname string `jsonapi:"attr,surname"`
}
func (user *User) JSONAPILinks() *jsonapi.Links {
	return &jsonapi.Links{
		"self": fmt.Sprintf("/api/v1/users/%d", user.ID),
	}
}

i use the method jsonapi.MarshalPayload to write in the body the slice of my struct []*User.
when i go to see the output i have this:

{
	"data": [
		{
			"type": "users",
			"id": "5614658570",
			"attributes": {
				"code": "testcode",
				"name": "test",
				"surname": "stest"
			},
			"links": {
				"self": "/api/v1/users/5614658570"
			}
		}
	]
}

all is correct but how can i insert the links in the root level (the same of "data") with the slice?

i seen this..

func Marshal(models interface{}) (Payloader, error) {
	switch vals := reflect.ValueOf(models); vals.Kind() {
	case reflect.Slice:
		m, err := convertToSliceInterface(&models)
		if err != nil {
			return nil, err
		}

		payload, err := marshalMany(m)
		if err != nil {
			return nil, err
		}

		if linkableModels, isLinkable := models.(Linkable); isLinkable {

if models is a slice, it can't to be Linkable, no?

Returning relationships without the related object

I'd like to return a named relationship with an ID, but not the object itself. For instance, if I have:

type Company struct {
	ID        string    `jsonapi:"primary,company"`
	Employees []*Person `jsonapi:"relation,employees"`
	Owners    []*Person `jsonapi:"relation,owners"`
}

I'd like to be able to return either Employees or Owners (or both) (or none) in the included array but in all cases return the relationships object with the correct ID. Is there some way to do that?

Thanks!!

Read-only for unmarshal

I want to use the Unmarshal funcs for updating a struct from user input but want to protect some fields, I'd like to be able to do something like:

func (h *ExampleHandler) updateBlog(w http.ResponseWriter, r *http.Request) {
	id := r.FormValue("id")

	// ...fetch your blog...

	intID, err := strconv.Atoi(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.update")

	// but, for now
	blog := fixtureBlogCreate(intID)

	if err := jsonapiRuntime.UnmarshalPayload(r.Body, blog); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}

	// persist updated blog...

	w.WriteHeader(http.StatusOK)
	w.Header().Set(headerContentType, jsonapi.MediaType)
	if err := jsonapiRuntime.MarshalPayload(w, blog); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Rather than creating a copy of the fields I don't want modifying and re-setting them after unmarshal.

I was thinking of adding another tag something like "readonly" that could be used e.g:

type Blog struct {
	ID            int       `jsonapi:"primary,blogs,readonly"`
	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,readonly"`
	ViewCount     int       `jsonapi:"attr,view_count,readonly"`
}

Allowing you to unmarshal into it safely without some fields being overridden. Usually with regular JSON I would implement UnmarshalJSON and handle it there, but I don't see how I could do this without changes to the library.

If I worked on this would you be interested in adding it to the project, or do you have a better solution of how to do this?

Thanks 👍

Select the relations to include

In json api specification, the user can select the relations to include.
/articles?include=author

with the actual methods in can choos to put or remove the include for all relations

in another specification the user can not select the field of relation but decide to include de relation:

/articles?include=author&fields[articles]=title,body&fields[people]=name
{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!",
      "body": "The shortest article. Ever."
    }
  }],
  "included": [
    {
      "type": "people",
      "id": "42",
      "attributes": {
        "name": "John"
      }
    }
  ]
}

Embedded structs are not being serialized.

type Foo struct {
     Name      string    `jsonapi:"attr,name"`
     CreatedOn time.Time `jsonapi:"attr,createdOn"`
}

type Bar struct {
     Foo    
     ID   string `jsonapi:"primary,bars"`
     Last string `jsonapi:"attr,last"`
}

Marshaling Bar struct, ignores embedded fields from Foo (i.e. name, createdOn)

Unmarshal of object/struct attributes fails

Hello,

I am trying to unmarshal a JSON API response which contains attributes that are objects. I tried marshaling and the result JSON is correct but when I try to unmarshal that same JSON it fails with data is not a jsonapi representation of '*main.Profile'. Here is some example code:

package main

import (
	"bytes"
	"fmt"
	"log"

	"github.com/google/jsonapi"
)

func main() {
	p := &Profile{ID: "1", Avatar: &Avatar{Large: "url1", Small: "url2"}}
	buf := &bytes.Buffer{}
	if err := jsonapi.MarshalOnePayload(buf, p); err != nil {
		log.Fatal(err)
	}

	p = &Profile{}
	fmt.Println(string(buf.Bytes()))
	err := jsonapi.UnmarshalPayload(buf, p)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("ID: %v, Avatar:%v\n", p.ID, p.Avatar)
}

type Profile struct {
	ID     string  `json:"id" jsonapi:"primary,profiles"`
	Avatar *Avatar `json:"avatar" jsonapi:"attr,avatar"`
}

type Avatar struct {
	Large string `json:"large"`
	Small string `json:"small"`
}

I also tried annotating with attr like

type Avatar struct {
	Large string `json:"large" jsonapi:"attr,large,omitempty"`
	Small string `json:"small" jsonapi:"attr,small,omitempty"`
}

but no luck.

Relationship links, without associated data

We're a bit stuck trying to unmarshal the links of a relationship.

Given this abbreviated example from the JSON-API spec:

{
  "type": "articles",
  "id": "1",
  "attributes": {
    "title": "JSON API paints my bikeshed!"
  },
  "relationships": {
    "author": {
      "links": {
        "self": "http://example.com/articles/1/relationships/author"
      }
    }
  }
}

Reading the spec, it states that an relationship entity with just links (e.g. author -> links -> self) is valid:

How would we read the self link of the Author relationship in this example above?

Here's an attempt of the structs, but this doesn't work:

type Article struct {
    ID     string  `jsonapi:"primary,articles"`
    Title  string  `jsonapi:"attr,title"`
    Author *Author `jsonapi:"relation,author"`
}

type Author struct {
    SelfLink string `jsonapi:"links,self"`
}

Add links/meta to the top document

Hi, I cant seems to create a response like the one bellow (origin JsonAPI site)

{
    "meta": {
        "total-pages": 13
    },
    "data": [{
        "type": "articles",
        "id": "3",
        "attributes": {
            "title": "JSON API paints my bikeshed!",
            "body": "The shortest article. Ever.",
            "created": "2015-05-22T14:56:29.000Z",
            "updated": "2015-05-22T14:56:28.000Z"
        }
    }],
    "links": {
        "self": "http://example.com/articles?page[number]=3&page[size]=1",
        "first": "http://example.com/articles?page[number]=1&page[size]=1",
        "prev": "http://example.com/articles?page[number]=2&page[size]=1",
        "next": "http://example.com/articles?page[number]=4&page[size]=1",
        "last": "http://example.com/articles?page[number]=13&page[size]=1"
    }
}

I can get the data but could not figure how to add meta or links to the response. I could only add them under the data level

This is the struct

type Album struct {
	ID   int    `jsonapi:"primary,album"`
	Name string `jsonapi:"attr,name"`
}

First I tried []*Album but coludnt understand how to add meta/links to the slice
Then I created a wrapper struct

type Albums struct {
	Albums []*Album `jsonapi:"relation,albums"`
}

But the output was invalid (or at least not like the provided example)

What am I missing. How can I add meta/links to the top level. Thanks.

Support customizing output format for time.Time

Current code takes time.Time values and writes that out as UNIX epoch times (integers).

It would be nice if I could specify (in the struct tag?) that I just want the time as-is, which shows a nice human-readable time instead of an integer.

Custom Error desired for unmarshal errors

It is desired to send a 400 status response for these types of errors when they occur in requests. However, the rudimentary use of:

err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type())

means the only way to determine this particular error means doing a regexp match on the error string.

Is it possible to Unmarshal into a map?

I can't seem to get this to work:

type Player struct {
    ID   int    `jsonapi:"primary,players"`
    Path string `jsonapi:"attr,path"`
    //Failing with data is not a jsonapi representation of '*main.Player'
    Files map[string]*File `jsonapi:"attr,files"`
}

type File struct {
    Path  string    `jsonapi:"attr,path"`
    isDir bool `jsonapi:"attr,isDir"`
    time  time.Time `jsonapi:"attr,time"`
}
{
    data: {
        type: "players",
        id: "71776400",
        attributes: {
            path: "/no/dnb/71776400",
            files: {
                meta.json.js: {
                    path: "/vlah/",
                    isDir: false,
                    time: "2016-10-04T00:59:12+00:00"
                },
                tine-sunniva: {
                    id: "6a342e32-3aad-41f2-86bd-721170d02718",
                    path: /blah",
                    isDir: true,
                    time: "2016-10-04T00:59:12+00:00",
                    atomic: true
                }
            }
        },
        links: {
            self: "https://blah/api/v2/players/71776400"
        }
    }
}

Running into an error while unmarshalling requests

Hello,

I was putting together a simple proof of concept API using this package for encoding/decoding JSON-API endpoints but I am coming across an issue related to ID numbers.

According to the spec here the id and type keys must be present within data. So following those guidelines, I send a request like so:

PATCH /path-to-endpoint HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
  "data": {
    "type": "enterprises",
    "id": "1154833676970755108",
    "attributes": {
      "description": "This is a test enterprise",
      "name": "Test Enterprise"
    }
  }
}

My struct looks like this:

type Enterprise struct {
    ID           int64      `jsonapi:"primary,enterprises"`
    InsertedByID *int64     `jsonapi:"attr,inserted-by-id"`
    UpdatedByID  *int64     `jsonapi:"attr,updated-by-id"`
    Name         *string    `jsonapi:"attr,name"`
    Description  *string    `jsonapi:"attr,description"`
    InsertedAt   *time.Time `jsonapi:"attr,inserted-at"`
    UpdatedAt    *time.Time `jsonapi:"attr,updated-at"`
}

Below is how I'm unmarshalling the record:

req := new(model.Enterprise)

if err := jsonapi.UnmarshalPayload(r.Body, req); err != nil {
    // Handling error here...
}

The exact error is id should be either string, int or uint. This error goes away if I do not include the ID in the request body, however since it is listed as a required part of the spec, I'd prefer not to deviate. It looks like the error is stemming from here. I looked through the code to see if there were any obvious issues I could find, or issues with my code but I couldn't find anything.

Is there something I'm doing wrong?

Can't get relation to work

I have this setup. I read from a json file with has the defaults. Can you see whats wrong? I can see that in UnmarshalPayload after the decode the relation values are not decoded to payload.

package model

type Get struct {
    Id      int      `jsonapi:"primary,get"`
    Title   string   `jsonapi:"attr,title"`
    Status  string   `jsonapi:"attr,status"`
    Success *Success `jsonapi:"relation,success"`
}

type Success struct {
    Id         string `jsonapi:"primary,success"`
    Systemtime int `jsonapi:"attr,systemtime"`
}
{
  "data": {
    "type": "get",
    "id": "1",
    "attributes": {
      "title": "title2",
      "status": "OK",
      "success": {
        "status": "OK",
        "systemtime": "1378304936929",
      }
    }
  }
}

Unmarshalling an array of objects?

If I've got an array back as my Json body, and I want to unmarshal that into an array of structs, it appears, according to your docs, that I can't do this. Is this feature coming soon?

Relationship Types

In the spec it allows the data object within any relationship to be a mix of any type. When defining the Struct Tags how would you go about doing this?

In this example a collection can have an entities relationship with ANY type which is what I need.

type Collection struct {
    ID          string        `json:"id" jsonapi:"primary,collection"`
    Title       string        `json:"title" jsonapi:"attr,title"`
    Description string        `json:"description" jsonapi:"attr,description"`
    Categories  []string      `json:"categories", jsonapi:"attr,categories"`
    Images      []*Image      `json:"images", jsonapi:"attr,images"`
    Entities    []interface{} `json:"entities", jsonapi:"relation:entities"`
}

I can't do this since a collection can contain both Videos and other Collections...

type Collection struct {
    ID          string        `json:"id" jsonapi:"primary,collection"`
    Title       string        `json:"title" jsonapi:"attr,title"`
    Description string        `json:"description" jsonapi:"attr,description"`
    Categories  []string      `json:"categories", jsonapi:"attr,categories"`
    Images      []*Image      `json:"images", jsonapi:"attr,images"`
    Entities    []*Video      `json:"entities", jsonapi:"relation:entities"`
}

Would this need to be done instead?

type Collection struct {
    ID             string        `json:"id" jsonapi:"primary,collection"`
    Title          string        `json:"title" jsonapi:"attr,title"`
    Description    string        `json:"description" jsonapi:"attr,description"`
    Categories     []string      `json:"categories", jsonapi:"attr,categories"`
    Images         []*Image      `json:"images", jsonapi:"attr,images"`
    Collections    []*Collection `json:"collections", jsonapi:"relation:entities"`
    Videos         []*Video      `json:"videos", jsonapi:"relation:entities"`
}

Simplification of Unmarshal API & Access to Meta and Links

With the recent simplification of the Marshal API (cf83b97) I think it might be a good time to consider something similar for the Unmarshal case.

While doing so, it would be even better to provide a solution for accessing the Meta and Links of the JSON API document (relevant issues #82, #68 and #64).

Right now there are two core functions that handle Unmarshaling:

UnmarshalPayload(in io.Reader, model interface{}) error

UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)

As long as the model is a pointer to a struct or a pointer to a slice, these can be simplified to one function with the same exact signature as UnmarshalPayload(in io.Reader, model interface{}) error. I have already done a proof of concept (nstratos/go-kitsu@8b8706c) for a package I am writing and it seems to be working fine.

The name could be kept the same for compatibility while emphasizing in the docs the new functionality of providing a pointer to slice to fill with data (instead of using UnmarshalManyPayload) or it could be made to new a function that covers both cases. For this discussion I am going to name this new function UnmarshalAll but it could be anything.

A separate but related issue (because of the function signature) is giving access to Meta and Links. The simplest way would be to return something extra besides the error. Perhaps something like this:

type Extras struct {
    Links *Links
    Meta *Meta
}

// Expecting v to be pointer to struct or pointer to slice.
func UnmarshalAll(r io.Reader, v interface{}) (Extras, error)

Thus with this function, the Unmarshal API gets simplified and access to Meta and LInks is provided as well.

Thoughts?

Bad JSON panics

I am about half way converting my API over to JSON-API compliant. Thanks so much for getting this project going.

In my testing, it seems JSON passed in without a data attribute throws a runtime error: invalid memory address or nil pointer dereference error. I think the library should handle this and pass back an error rather than panicking.

I played with how to do this a bit and it is pretty easy to add a nil check and return an error around https://github.com/shwoodard/jsonapi/blob/master/request.go#L63. However that would give some potentially noncompliant results when a {"data": null} is actually expected. It looks like it's possible on updates. http://jsonapi.org/format/#crud-updating-relationships

Maybe I am overthinking this since my server go routine would just fail and 500 to the consumer, but I don't think the server should panic every time someone sends in non JSON-API compliant JSON.

I played around in a brach to see if I could fix it quickly. The only idea I had was to use unmarshal vs decode and test the JSON if it actually had a Data attribute.

dustinsmith1024@0659da4

Panic recovery -> reflect: call of reflect.Value.Elem on struct Value

I'm getting this error: panic recovery -> reflect: call of reflect.Value.Elem on struct Value when using MarshalMany() method.

Here's the code:

type League2 struct {
    ID   int64  `json:"id jsonapi:"primary,leagues"`
    Name string `jsonapi:"attr,name"`
}

var leagues []model.League2

err := Db.Table("league").Find(&leagues).Error
if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{})
    return
}

leagueInterface := make([]interface{}, len(leagues))
for i, league := range leagues {
    leagueInterface[i] = league
}

leagues2, err := jsonapi.MarshalMany(leagueInterface)

Any idea why?

I'm using gin framework and gorm btw.

Error: data is not a jsonapi representation of 'model.League'

POST /api/leagues

Payload:

{
    "data": {
        "type": "leagues",
        "attributes": {
            "name": "test"
        }
    }
}
type League struct {
    ID   int64  `sql:"id;AUTO_INCREMENT" json:"id jsonapi:"primary,leagues"`
    Name string `json:"name" jsonapi:"attr,name"`
    Type string `json:"type jsonapi:"attr,type"`
}

func LeagueCreate(c *gin.Context) {
    var league League

    fmt.Println("c.Request.Body")
    fmt.Printf("%+v\n", c.Request.Body)

    err := jsonapi.UnmarshalPayload(c.Request.Body, league)
    if err != nil {
        fmt.Println(err)
        c.JSON(500, gin.H{})
        return
    }
}

Error: Error: data is not a jsonapi representation of 'model.League'

Output of fmt.Printf("%+v\n", c.Request.Body) : &{src:0xc20812b0e0 hdr:<nil> r:<nil> closing:false mu:{state:0 sema:0} closed:false}

I'm stuck. What am I doing wrong? I'm using Gin framework.

omitempty for primary ID

I'm using jsonapi to construct jsonapi documents as an API client and when marshaling a resource for creation I'd like to omit the "id" since the implementation I'm using requires that (and appears spec compliant as far as I can tell).

To do this at the moment I have to reimplement OnePayload to have omitempty on the ID manually.

Proposal: omitempty removal for relations

Proposal

This issue was filed as a proposal for the removal of the omitempty as an option in a relation tag.

Problem

To One

Say an application had the following structs, modeling a Book and the Author that wrote it:

type Book struct {
	ISBN   string  `jsonapi:"primary,book"`
	Title  string  `jsonapi:"attr,title"`
	Author *Author `jsonapi:"relation,author"`
}

type Author struct {
	ID   string `jsonapi:"primary,author"`
	Name string `jsonapi:"attr,name"`
}

Currently the application would be able to serialize the struct in two different ways:

1. Book had an Author:

  • Go:
b := &Book{
	ISBN:  "0-676-97376-0",
	Title: "Life of Pi",
	Author: &Author{
		ID:   "1",
		Name: "Yann Martel",
	},
}
  • JSON:
{
    "data": {
        "type": "book",
        "id": "0-676-97376-0",
        "attributes": {
            "title": "Life of Pi"
        },
        "relationships": {
            "author": {
                "data": {
                    "type": "author",
                    "id": "1"
                }
            }
        }
    },
    "included": [{
        "type": "author",
        "id": "1",
        "attributes": {
            "name": "Yann Martel"
        }
    }]
}

2. Book without an Author (anonymous):

  • Go:
b := &Book{
	ISBN:  "978-0615275062",
	Title: "Diary of an Oxygen Thief",
}
  • JSON:
{
  "data": {
      "type": "book",
      "id": "978-0615275062",
      "attributes": {
          "title": "Diary of an Oxygen Thief"
      },
      "relationships": {
          "author": {
              "data": null
          }
      }
  }
}

The client is to understand the first example as the Book in the payload was authored by Yann Martel. The second example is understood by the client as there was no Author for this Book. When the omitempty option is added to the Book's Author field:

type Book struct {
	ISBN   string  `jsonapi:"primary,book"`
	Title  string  `jsonapi:"attr,title"`
	Author *Author `jsonapi:"relation,author,omitempty"`
}

The representation of a Book without an Author is no longer possible. When the relation is nil the relation will be omitted from the JSON Payload:

b := &Book{
	ISBN:  "978-0615275062",
	Title: "Diary of an Oxygen Thief",
}
{
    "data": {
        "type": "book",
        "id": "978-0615275062",
        "attributes": {
            "title": "Diary of an Oxygen Thief"
        }
    }
}

With the omitempty tag present in the relation, the ability to represent an empty to-one relationships via a null resource linkage has been lost.

To Many

The same problem is present with to-many relations, lets say an application models Teams and Playerss:

type Team struct {
	ID      string    `jsonapi:"primary,team"`
	Name    string    `jsonapi:"attr,name"`
	Players []*Player `jsonapi:"relation,players"`
}

type Player struct {
	ID   string `jsonapi:"primary,player"`
	Name string `jsonapi:"attr,name"`
}

The application would be able to serialize a Team structs' Players in two ways:

1. Team had Playerss:

  • Go:
t := &Team{
	ID:   "tor",
	Name: "Toronto Maple Leafs",
	Players: []*Player{
		&Player{
			ID:   "34",
			Name: "Auston Matthews",
		},
		&Player{
			ID:   "16",
			Name: "Mitchell Marner",
		},
	},
}
  • JSON:
{
    "data": {
        "type": "team",
        "id": "tor",
        "attributes": {
            "name": "Toronto Maple Leafs"
        },
        "relationships": {
            "players": {
                "data": [{
                    "type": "player",
                    "id": "34"
                }, {
                    "type": "player",
                    "id": "16"
                }]
            }
        }
    },
    "included": [{
        "type": "player",
        "id": "34",
        "attributes": {
            "name": "Auston Matthews"
        }
    }, {
        "type": "player",
        "id": "16",
        "attributes": {
            "name": "Mitchell Marner"
        }
    }]
}

2. Team did not have any Players (ie expansion team):

  • Go:
t := &Team{
	ID:      "vgk",
	Name:    "Vegas Golden Knights",
	Players: []*Player{},
}
  • JSON:
{
    "data": {
        "type": "team",
        "id": "vgk",
        "attributes": {
            "name": "Vegas Golden Knights"
        },
        "relationships": {
            "players": {
                "data": []
            }
        }
    }
}

The client is to understand the first example as the Team in the payload had 2 Players. The second example is understood by the client as there were no Players for this Team. When the omitempty option is added to the Team's Players field:

type Team struct {
	ID      string    `jsonapi:"primary,team"`
	Name    string    `jsonapi:"attr,name"`
	Players []*Player `jsonapi:"relation,players,omitempty"`
}

The representation of a Team without Players is no longer possible. When the relation is empty ([]*Player{}) the relation will be omitted from the JSON Payload:

t := &Team{
	ID:      "vgk",
	Name:    "Vegas Golden Knights",
	Players: []*Player{},
}
{
    "data": {
        "type": "team",
        "id": "vgk",
        "attributes": {
            "name": "Vegas Golden Knights"
        }
    }
}

With the omitempty tag present in the relation, the ability to represent an empty to-many relationships via an empty array [ ] resource linkage has been lost.

Solution

From here down the code samples are relying on 1.8rc3; to install it do:

go get golang.org/x/build/version/go1.8rc3 && go1.8rc3 download

Starting in go1.8 struct tags will be ignored during type conversions (see golang/go#16085 for commentary). This means you can have something like:

type foo struct {
	ID string `test:"foo"`
}
type bar struct {
	ID string `test:"bar"`
}

f := foo{ID: "foo"}
b := bar(f)
fmt.Println(f, b)

Go 1.8rc3 output:

{foo} {foo}

Go 1.7.5 would have resulted in an error of cannot convert f (type foo) to type bar

Why does this help? This helps easily convert between different permutations of structs:

To One

Define:

type Book struct {
	ISBN   string  `jsonapi:"primary,book"`
	Title  string  `jsonapi:"attr,title"`
	Author *Author
}

type BookAndAuthor struct {
	ISBN   string  `jsonapi:"primary,book"`
	Title  string  `jsonapi:"attr,title"`
	Author *Author `jsonapi:"relation,author"`
}

Think of both of these structs as JSON presenters.

  • Book would be used when you want to send/receive a representation of a book without an Author over the wire - irrespective of whether or not the book actually even has an author.
  • BookAndAuthor would be used when you want to send/receive a representation of a book with an Author over the wire - again irrespective of whether or not that particular book had an author.

Converting between these is as easy as:

b1 := Book{
	ISBN:  "978-0615275062",
	Title: "Diary of an Oxygen Thief",
}
b2 := BookAndAuthor(b)

Depending on your server's/client's intention on what you need to send/receive over the wire you would pick the appropriate struct to use. If you only ever need to send/receive a Book with an Author you would only have the BookAndAuthor struct (you would probably also rename it to just Book at that point also). In the example above b2 would have been serialized.

Conversely, if you only ever intend send/receive a Book without the Author on the wire, you would only have the Book struct. In the example above that means you would serialize b1.

To Many

type Team struct {
	ID      string    `jsonapi:"primary,team"`
	Name    string    `jsonapi:"attr,name"`
	Players []*Player
}

type TeamAndPlayers struct {
	ID      string    `jsonapi:"primary,team"`
	Name    string    `jsonapi:"attr,name"`
	Players []*Player `jsonapi:"relation,players"`
}

Again each struct is used depending on the server's/client's intention. If you don't want Players on the wire use Team; if you do want Players on the wire use TeamAndPlayers. Again the conversion between them is trivial.

Summary

I think this solution makes more sense than the omitempty tag for these reasons:

  • Allows for the representation of empty relations; the omitempty tag obfuscates this complication
  • Even if omitempty was used in combination with go1.8, 2 structs would still be needed to cover all cases
  • Separates the logical representation of different JSON API schemas into two structs for use in distinct scenarios
  • Omitting the jsonapi struct tags is more intuitive than looking through the documentation/source to discover the omitempty functionality for a relation

Need support for `omitempty` & `-` on attr fields.

There are conditions where a model is used in different circumstances, for different responses, and a field may be present, or not present depending on the circumstance. Standard library supports the omitempty and - tokens in the json:"" tag. It would be great to offer the same thing here.

Maybe a third value can be passed: jsonapi:"attr,myfield,omitempty" or something of the sort.

UnmarshalManyPayload with Links

Hi,

I've been trying to use the jsonapi for marshaling and unmarshaling a JSON object containing links, and I am able to marshal the JSON object (and have the links section in it), however, I am not able to unmarshal and extract the link from the JSON object.

I am unable to find any code sample, even in the tests, to see how it could be done. Is this information lost when the unmarshal is done through this library? is there any workaround which is not clear?

Thanks.

Support for pagination?

Issue

It is not understandable from the docs as to how to implement pagination using this library. Is the pagination feature missing from the library? If not, some example or documentation on this will be super useful.

Includes have random order

Hi all, I'm having trouble testing my JSONAPI response as the order in included changes randomly 😢
What is the logic there and is there a workaround for it? I'm using MarshalOnePayload.

Cheers

Unmarshal access to "meta"

It'd be great to have access to an annotation type "meta" or some other way to easily access the Meta information from a JSON API document.

Currently I'm unmarshaling twice: once into OnePayload via encoding/json and again via the jsonapi unmarshal methods. It'd be wonderful to button this up.

Pagination access

Great library thank you!

I wanted to ask, is there any way to access / unmarshal the pagination links that get sent by jsonapis like these:

"links": {
    "first": "http://somesite.com/movies?page[limit]=50&page[offset]=50",
    "prev": "http://somesite.com/movies?page[limit]=50&page[offset]=0",
    "next": "http://somesite.com/movies?page[limit]=50&page[offset]=100",
    "last": "http://somesite.com/movies?page[limit]=50&page[offset]=500"
 }

Ideally I'd like to provide access to these offsets in a separate struct as unmarshaling them into the Movie struct sounds strange! Thanks!

Stack overflow with many-to-many relationships

Is it possible to marshal a many-to-many relationship with jsonapi? I end up getting a stack overflow panic when trying to do so.

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x4f5efb, 0xe)
	/usr/lib/go/src/runtime/panic.go:596 +0x95
runtime.newstack(0x0)
	/usr/lib/go/src/runtime/stack.go:1089 +0x3f2
runtime.morestack()
	/usr/lib/go/src/runtime/asm_amd64.s:398 +0x86

goroutine 1 [running]:
runtime.heapBitsSetType(0xc431465130, 0x50, 0x50, 0x4e8040)
	/usr/lib/go/src/runtime/mbitmap.go:894 +0x64b fp=0xc440100340 sp=0xc440100338
runtime.mallocgc(0x50, 0x4e8040, 0x1, 0x0)
	/usr/lib/go/src/runtime/malloc.go:723 +0x5c4 fp=0xc4401003e0 sp=0xc440100340
runtime.newobject(0x4e8040, 0x0)
	/usr/lib/go/src/runtime/malloc.go:820 +0x38 fp=0xc440100410 sp=0xc4401003e0
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:207 +0x70 fp=0xc4401007c0 sp=0xc440100410
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440100848 sp=0xc4401007c0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440100bf8 sp=0xc440100848
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440100c80 sp=0xc440100bf8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440101030 sp=0xc440100c80
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401010b8 sp=0xc440101030
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440101468 sp=0xc4401010b8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401014f0 sp=0xc440101468
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401018a0 sp=0xc4401014f0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440101928 sp=0xc4401018a0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440101cd8 sp=0xc440101928
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440101d60 sp=0xc440101cd8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440102110 sp=0xc440101d60
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440102198 sp=0xc440102110
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440102548 sp=0xc440102198
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401025d0 sp=0xc440102548
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440102980 sp=0xc4401025d0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440102a08 sp=0xc440102980
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440102db8 sp=0xc440102a08
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440102e40 sp=0xc440102db8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401031f0 sp=0xc440102e40
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440103278 sp=0xc4401031f0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440103628 sp=0xc440103278
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401036b0 sp=0xc440103628
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440103a60 sp=0xc4401036b0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440103ae8 sp=0xc440103a60
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440103e98 sp=0xc440103ae8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440103f20 sp=0xc440103e98
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401042d0 sp=0xc440103f20
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440104358 sp=0xc4401042d0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440104708 sp=0xc440104358
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440104790 sp=0xc440104708
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440104b40 sp=0xc440104790
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440104bc8 sp=0xc440104b40
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440104f78 sp=0xc440104bc8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440105000 sp=0xc440104f78
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401053b0 sp=0xc440105000
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440105438 sp=0xc4401053b0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401057e8 sp=0xc440105438
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440105870 sp=0xc4401057e8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440105c20 sp=0xc440105870
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440105ca8 sp=0xc440105c20
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440106058 sp=0xc440105ca8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401060e0 sp=0xc440106058
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440106490 sp=0xc4401060e0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440106518 sp=0xc440106490
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401068c8 sp=0xc440106518
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440106950 sp=0xc4401068c8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440106d00 sp=0xc440106950
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440106d88 sp=0xc440106d00
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440107138 sp=0xc440106d88
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401071c0 sp=0xc440107138
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440107570 sp=0xc4401071c0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401075f8 sp=0xc440107570
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401079a8 sp=0xc4401075f8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440107a30 sp=0xc4401079a8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440107de0 sp=0xc440107a30
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440107e68 sp=0xc440107de0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440108218 sp=0xc440107e68
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401082a0 sp=0xc440108218
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440108650 sp=0xc4401082a0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401086d8 sp=0xc440108650
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440108a88 sp=0xc4401086d8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440108b10 sp=0xc440108a88
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440108ec0 sp=0xc440108b10
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440108f48 sp=0xc440108ec0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc4401092f8 sp=0xc440108f48
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440109380 sp=0xc4401092f8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440109730 sp=0xc440109380
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc4401097b8 sp=0xc440109730
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440109b68 sp=0xc4401097b8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc440109bf0 sp=0xc440109b68
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc440109fa0 sp=0xc440109bf0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010a028 sp=0xc440109fa0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010a3d8 sp=0xc44010a028
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010a460 sp=0xc44010a3d8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010a810 sp=0xc44010a460
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010a898 sp=0xc44010a810
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010ac48 sp=0xc44010a898
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010acd0 sp=0xc44010ac48
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010b080 sp=0xc44010acd0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010b108 sp=0xc44010b080
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010b4b8 sp=0xc44010b108
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010b540 sp=0xc44010b4b8
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010b8f0 sp=0xc44010b540
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010b978 sp=0xc44010b8f0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010bd28 sp=0xc44010b978
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010bdb0 sp=0xc44010bd28
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010c160 sp=0xc44010bdb0
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010c1e8 sp=0xc44010c160
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010c598 sp=0xc44010c1e8
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010c620 sp=0xc44010c598
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010c9d0 sp=0xc44010c620
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc280, 0xc4200142e8, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc280)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010ca58 sp=0xc44010c9d0
github.com/google/jsonapi.visitModelNode(0x4c8de0, 0xc4200142d0, 0xc4600ffca8, 0x4c8d01, 0xc4200142d0, 0xc42000e430, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010ce08 sp=0xc44010ca58
github.com/google/jsonapi.visitModelNodeRelationships(0x4cc2c0, 0xc420014288, 0x197, 0xc4600ffca8, 0x1, 0x0, 0x2, 0x4cc2c0)
	/home/jack/go/src/github.com/google/jsonapi/response.go:487 +0x116 fp=0xc44010ce90 sp=0xc44010ce08
github.com/google/jsonapi.visitModelNode(0x4c8da0, 0xc420014270, 0xc4600ffca8, 0x4c8d01, 0xc420014270, 0xc42000e420, 0x196)
	/home/jack/go/src/github.com/google/jsonapi/response.go:390 +0x1bc8 fp=0xc44010d240 sp=0xc44010ce90
...additional frames elided...
exit status 2
package main

import (
	"bytes"
	"fmt"
	"github.com/google/jsonapi"
	"os"
)

type Post struct {
	ID      int       `jsonapi:"primary,posts"`
	Title   string    `jsonapi:"attr,name"`
	Authors []*Author `jsonapi:"relation,authors"`
}

type Author struct {
	ID    int     `jsonapi:"primary,authors"`
	Name  string  `jsonapi:"attr,name"`
	Posts []*Post `jsonapi:"relation,posts"`
}

func main() {
	jack := Author{
		ID:   1,
		Name: "Jack Wilsdon",
	}

	john := Author{
		ID:   2,
		Name: "John O'Connors",
	}

	marshal := Post{
		ID:      1,
		Title:   "Marshalling into JSON",
		Authors: []*Author{&jack, &john},
	}

	weather := Post{
		ID:      2,
		Title:   "Go 101",
		Authors: []*Author{&jack},
	}

	jack.Posts = []*Post{&marshal, &weather}
	john.Posts = []*Post{&marshal}

	buffer := bytes.Buffer{}

	if err := jsonapi.MarshalPayload(&buffer, &jack); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Println(buffer.String())
}

empty relation attributes

After deserializing single record json with included data relation's attributes are still empty.
Reproduced here.

models:

type Author struct {
	ID        string  `jsonapi:"primary,authors"`
	FirstName string  `jsonapi:"attr,first_name"`
	LastName  string  `jsonapi:"attr,last_name"`
	Posts     []*Post `jsonapi:"relation,posts"`
}

type Post struct {
	ID    string `jsonapi:"primary,posts"`
	Title string `jsonapi:"attr,title"`
	Text  string `jsonapi:"attr,text"`
}

input json:

{
    "data": {
    "id":"123",
    "type":"authors",
    "attributes": {
        "first_name": "John",
        "last_name": "Doe"
    },
    "relationships": {
        "posts": {
        "data": [{
            "id": "456",
            "type": "posts"
        }],
        "links": {
            "self": "http://example.com/api/authors/123/posts",
            "related": "http://example.com/api/authors/123/relationships/posts"
        }
        }
    },
    "links": {
        "self": "http://example.com/api/authors/123"
    },
    "meta": {
        "includes": ["posts"],
        "duration": 600
    },
    "included":[{
        "id":"456",
        "type":"posts",
        "attributes": {
        "title":"Foo",
        "text":"bar baz boo!"
        }
    }]
    }
}

after deserializing it looks like this:

fmt.Printf("%#v\n", author)
// main.Author{ID:"123", FirstName:"John", LastName:"Doe", Posts:[]*main.Post{(*main.Post)(0xc420010ba0)}}
fmt.Printf("%#v\n", *author.Posts[0])
// main.Post{ID:"456", Title:"", Text:""}

jsonapi:relation causes Reflect: call of reflect.Value.Elem on struct Value

Hello, I'm new to the Go language and I'm still adapting. I started some tests and had difficulties in making relationships between types using a jsonapi StructTag.

It is giving panic serving to reflect: call to reflect.Value.Elem about struct value

type Post struct {
	Id 		int 	`jsonapi:"primary,posts"`
	Title 		string 	`jsonapi:"attr,title"`
	User		*User	`jsonapi:"relation,user"`
}

type User struct {
	Id 		int 	`jsonapi:"primary,users"`
	Username	string 	`jsonapi:"attr,username"`
	Password	string	`jsonapi:"attr,password"`
}

func handler(w http.ResponseWriter, r *http.Request){
	post := Post{Id: 1, Title: "Title", User: &User{Id:1, Username:"username", Password:"password"}}
	jsonapi.MarshalOnePayload(w, post);
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8000", nil)
}

Is causing this

2017/01/12 20:15:48 http: panic serving 127.0.0.1:33616: reflect: call of reflect.Value.Elem on struct Value
goroutine 20 [running]:
net/http.(*conn).serve.func1(0xc420078280)
	/usr/local/go/src/net/http/server.go:1491 +0x12a
panic(0x62c2c0, 0xc4200de760)
	/usr/local/go/src/runtime/panic.go:458 +0x243
reflect.Value.Elem(0x649420, 0xc4200de740, 0x99, 0xc4200de740, 0x99, 0x200000003)
	/usr/local/go/src/reflect/value.go:734 +0x114
github.com/google/jsonapi.visitModelNode(0x649420, 0xc4200de740, 0xc420038b50, 0x1, 0xc42007b140, 0x20, 0xc4200de740)
	/home/robertodantas/work/src/github.com/google/jsonapi/response.go:192 +0xc2
github.com/google/jsonapi.MarshalOne(0x649420, 0xc4200de740, 0xc420060000, 0xc420038bb8, 0x410a55)
	/home/robertodantas/work/src/github.com/google/jsonapi/response.go:77 +0x7d
github.com/google/jsonapi.MarshalOnePayload(0x7771c0, 0xc42007da00, 0x649420, 0xc4200de740, 0xc4200de740, 0xc4200a0110)
	/home/robertodantas/work/src/github.com/google/jsonapi/response.go:39 +0x42
main.handler(0x77a8c0, 0xc42007da00, 0xc4200e20f0)
	/home/robertodantas/work/src/github.com/7robertodantas/api/api.go:22 +0x185
net/http.HandlerFunc.ServeHTTP(0x696898, 0x77a8c0, 0xc42007da00, 0xc4200e20f0)
	/usr/local/go/src/net/http/server.go:1726 +0x44
net/http.(*ServeMux).ServeHTTP(0x794ee0, 0x77a8c0, 0xc42007da00, 0xc4200e20f0)
	/usr/local/go/src/net/http/server.go:2022 +0x7f
net/http.serverHandler.ServeHTTP(0xc420078200, 0x77a8c0, 0xc42007da00, 0xc4200e20f0)
	/usr/local/go/src/net/http/server.go:2202 +0x7d
net/http.(*conn).serve(0xc420078280, 0x77ae00, 0xc4200743c0)
	/usr/local/go/src/net/http/server.go:1579 +0x4b7
created by net/http.(*Server).Serve
	/usr/local/go/src/net/http/server.go:2293 +0x44d

Could someone help me and tell me what I'm doing wrong?

Unmarshalling Payload for jsonAPI PATCH

When unmarshalling a payload for PATCH that only contains some of the fields, how do you recommend we differentiate between values not included in the PATCH and values included but blank. The resulting Go struct will initialize all fields to 0-values and this make it difficult to properly implement the json API patch as per http://jsonapi.org/format/#crud-updating

Nested structs not being Unmarshaled

Given a struct that contains another struct:

type Address struct {
    City string `jsonapi:"attr,city" json:"city"`
    State string `jsonapi:"attr,state" json:"state"`
}

type AdditionalInfo struct {
    HomeAddress     *Address `jsonapi:"attr,homeaddress" json:"homeaddress"`
        Phone                  *string     `jsonapi:"attr,phone" json:"phone"`
}

When calling UnmarshalPayload with this json:

{
    "homeaddress": {
        "city": "brooklyn",
        "state": "NY"
    },
    "phone": "555-555-5555"
}

I get an error data is not a jsonapi representation of AdditionalInfo. Are nested structs not supported?

What i am doing wrong ?

Probably its not a bug just don’t know how to use it

Can you guys point me in some direction

Here is my case

type Domain struct {
    ID        int       `gorm:"primary_key" sql:"AUTO_INCREMENT" json:"id" jsonapi:"primary,domains"`
    CreatedAt time.Time \`sql:"DEFAULT:current_timestamp" json:"created_at" jsonapi:"attr,created_at"\`
    UpdatedAt time.Time `json:"updated_at" jsonapi:"attr,updated_at"`
    Name                string        `gorm:"type:varchar(255);unique_index;not null" json:"name" jsonapi:"attr,name"`
    Active              bool          `gorm:"not null" sql:"DEFAULT:true" json:"active" jsonapi:"attr,active"`
    Description         string        `gorm:"type:varchar(255)" json:"description" jsonapi:"attr,description"`
    SenderBccAddress    string        `gorm:"type:varchar(255)" json:"sender_bcc_address" jsonapi:"attr,sender_bcc_address"`
    RecipientBccAddress string        `gorm:"type:varchar(255)" json:"recipient_bcc_address" jsonapi:"attr,recipient_bcc_address"`
    Backupmx            bool          `gorm:"not null" sql:"DEFAULT:false" json:"backupmx jsonapi:"attr,backupmx"`
    Transport           string        `gorm:"type:varchar(50);not null" sql:"DEFAULT:'dovecot'" json:"transport" jsonapi:"attr,transport"`
}

Fetching data :

func listDomains(w http.ResponseWriter, r *http.Request) {
    jsonapiRuntime := jsonapi.NewRuntime().Instrument("domains.list")
    var domains []models.Domain
    Rdbms.Find(&domains)
    domainInterface := make([]interface{}, len(domains))
    for i, domain := range domains {
    domainInterface[i] = domain
    }
    Log.Infof(" >>>>>>>>>>>> %q",domainInterface)
    w.WriteHeader(200)
    w.Header().Set("Content-Type", "application/vnd.api+json")
    if err := jsonapiRuntime..MarshalManyPayload(w, domainInterface); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

The error :

`2016-07-15T16:02:17.502 listDomains ▶ INFO   >>>>>>>>>>>> [{'\x01' "2016-07-14 22:40:39 +0000 UTC" "2016-07-14 22:40:39 +0000 UTC" "servr-lab.info" %!q(bool=true) "" "" "" %!q(bool=false) "dovecot"}]
2016/07/15 16:02:17 http: panic serving 89.25.32.29:36542: reflect: call of reflect.Value.Elem on struct Value
goroutine 14 [running]:
net/http.(*conn).serve.func1(0xc82016d580)
        /root/.gvm/gos/go1.6/src/net/http/server.go:1389 +0xc1
panic(0x8d42a0, 0xc8201a1380)
        /root/.gvm/gos/go1.6/src/runtime/panic.go:426 +0x4e9
reflect.Value.Elem(0x956240, 0xc82014d180, 0x99, 0x0, 0x0, 0x0)
        /root/.gvm/gos/go1.6/src/reflect/value.go:735 +0x228
github.com/shwoodard/jsonapi.visitModelNode(0x956240, 0xc82014d180, 0xc82046d480, 0x1, 0xc8204443f0, 0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/response.go:173 +0x106
github.com/shwoodard/jsonapi.MarshalMany(0xc820168350, 0x1, 0x1, 0x30, 0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/response.go:122 +0xe9
github.com/shwoodard/jsonapi.MarshalManyPayload(0x7fd17bd3e5e0, 0xc82018c4e0, 0xc820168350, 0x1, 0x1, 0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/response.go:100 +0x57
github.com/shwoodard/jsonapi.(*Runtime).MarshalManyPayload.func1(0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/runtime.go:71 +0x64
github.com/shwoodard/jsonapi.(*Runtime).instrumentCall(0xc8201ac368, 0x2, 0x3, 0xc82046d698, 0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/runtime.go:83 +0x59
github.com/shwoodard/jsonapi.(*Runtime).MarshalManyPayload(0xc8201ac368, 0x7fd17bd3e5e0, 0xc82018c4e0, 0xc820168350, 0x1, 0x1, 0x0, 0x0)
        /srv/projects/vmail/src/github.com/shwoodard/jsonapi/runtime.go:72 +0x93
vmail/core.listDomains(0x7fd17bd3e2b0, 0xc82018c4e0, 0xc8201b8540)`

Unmarshal methods should also return pointers to Payload structs

According to the jsonapi spec (http://jsonapi.org/format/#crud-updating), clients may update resources with only a subset of attributes for the resource, and the server MUST treat missing attributes as if they were included with their existing values. In order to achieve this, it's necessary to know what attributes were actually included in the original request.

My suggestion to achieve this would be for the UnmarshalPayload/UnmarshalManyPayload methods to return the OnePayload/ManyPayload structs in addition to the current arguments.

func UnmarshalPayload(in io.Reader, model interface{}) (*OnePayload, error)
...
func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, *ManyPayload, error) {

Obviously, this would break compatibility, so new methods? If this makes sense, I don't mind working on a pull request.

Date format, Unix time only?

It seems this package only supports serializing date fields as unix time, but we have other consumers of the API that rely on a different format. Why force a strict format? I was able to fix this with a patch... but there should be a better way.

runtime error: comparing uncomparable type Garden.Gardens

This struct point to another struct in another package

type User struct {
	ID        int64  `jsonapi:"primary,user"`
	Name      string `jsonapi:"attr,name"`
	Gardens   Garden.Gardens `jsonapi:"attr,garden,omitempty"`
}

The Garden.Gardens type is:

type Gardens []*Garden

When the code reach

	return jsonapi.MarshalPayload(c.Response(), &u)

I get

echo: http2: panic serving 127.0.0.1:40812: runtime error: comparing uncomparable type Garden.Gardens
goroutine 21 [running]:
net/http.(*http2serverConn).runHandler.func1(0xc420216008, 0xc42025bfaf, 0xc42020e000)
/opt/go/src/net/http/h2_bundle.go:4604 +0x190
panic(0x78f880, 0xc4201c9f10)
/opt/go/src/runtime/panic.go:489 +0x2cf
github.com/google/jsonapi.visitModelNode(0x7cd5a0, 0xc4202620a0, 0xc42025b958, 0x1, 0xc4201cb770, 0xc4202620a0, 0x16)
/path/src/github.com/google/jsonapi/response.go:344 +0xaab
github.com/google/jsonapi.marshalOne(0x7cd5a0, 0xc4202620a0, 0x16, 0x7e21a0, 0xc4202620a0)
/path/src/github.com/google/jsonapi/response.go:143 +0x7d
github.com/google/jsonapi.Marshal(0x7cd5a0, 0xc4202620a0, 0xc42025ba28, 0x42b82b, 0x7fc100, 0x1)
/path/src/github.com/google/jsonapi/response.go:111 +0x34c
github.com/google/jsonapi.MarshalPayload(0x98e4a0, 0xc42023a050, 0x7cd5a0, 0xc4202620a0, 0xc4200b4c60, 0xa5)
/path/src/github.com/google/jsonapi/response.go:66 +0x4d

If i replace the attr with relation it works as expected:

	Gardens   Garden.Gardens `jsonapi:"relation,garden,omitempty"`

Is it a bug or maybe misuse on my side?

Unmarshal relationships with null data

Greetings,

I am trying to unmarshal a payload that includes relationships with null data like this one:

{
   "data":{
      "type":"movie",
      "id":"1",
      "relationships":{
         "cast":{
            "data":[
               {
                  "type":"cast",
                  "id":"5"
               },
               {
                  "type":"cast",
                  "id":"6"
               }
            ]
         }
      }
   },
   "included":[
      {
         "type":"cast",
         "id":"5",
         "relationships":{
            "person":{
               "data":null
            }
         }
      },
      {
         "type":"cast",
         "id":"6",
         "relationships":{
            "person":{
               "data":{
                  "type":"person",
                  "id":"33"
               }
            }
         }
      },
      {
         "type":"person",
         "id":"33",
         "attributes":{
            "name":"john"
         }
      }
   ]
}

According to the JSON API spec, a resource linkage can be null for empty to-one relationships.

For convenience I've added some code that tries to unmarshal such a payload on the Go playground.

When using jsonapi.UnmarshalPayload or jsonapi.UnmarshalManyPayload to handle payload like the above, the error data is not a jsonapi representation of 'xxx' is returned.

The culprit seems to be this line which receives a nil relationships.Data from this line. A nil check fixed the problem for my case but there might be other areas in the package that are affected so I figured I should report it here.

Thank you

Link support

It seems there is no links support. Any chance to have it?

Option to omit include from marshal

I think it should be possible to omit include. I have been using MarshalOnePayloadEmbedded, but it's an ugly workaround and doesn't work for MarshalMany.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.