JSONAPI.org Implementation for Go
A JSON API Implementation for Go, to be used e.g. as server for Ember Data.
For the complete api2go package use:
go get github.com/manyminds/api2goIf you only need marshalling and/or unmarshalling:
go get github.com/manyminds/api2go/jsonapi Api2go will Marshal/Unmarshal exactly like the internal json package from Go
with one addition: It will decorate the Marshalled json with jsonapi meta
objects. Jsonapi wraps the payload inside an attributes object. The rest is
just Meta-Data which will be generated by api2go.
So let's take this basic example:
type Article struct {
ID string
Title string `json:"title"`
}Would json.Marshal into this Json:
{
"ID": "Some-ID",
"title": "the title"
}For api2go, you have to ignore tag the ID field and then the result could be
something like this:
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
}
}All the additional information is retrieved by implementing some interfaces.
For the following query and result examples, imagine the following 2 structs which represent a posts and comments that belong with a has-many relation to the post.
type Post struct {
ID int `json:"-"` // Ignore ID field because the ID is fetched via the
// GetID() method and must not be inside the attributes object.
Title string `json:"title"`
Comments []Comment `json:"-"` // this will be ignored by the api2go marshaller
CommentsIDs []int `json:"-"` // it's only useful for our internal relationship handling
}
type Comment struct {
ID int `json:"-"`
Text string `json:"text"`
}You must at least implement the MarshalIdentifier interface, which is the one for marshalling/unmarshalling the primary ID of the struct
that you want to marshal/unmarshal. This is because of the huge variety of types that you could use for the primary ID. For example a string,
a UUID or a BSON Object for MongoDB etc...
In the Post example struct, the ID field is ignored because api2go will use the GetID method that you implemented
for your struct to fetch the ID of the struct.
Every field inside a struct will be marshalled into the attributes object in
the json. In our example, we just want to have the Title field there.
Don't forget to name all your fields with the json:"yourName" tag.
type Responder interface {
Metadata() map[string]interface{}
Result() interface{}
StatusCode() int
}The Responder interface must be implemented if you are using our API. It contains everything that is needed for a response. You can see an example usage of it in our example project.
type EntityNamer interface {
GetName() string
}EntityNamer is an optional interface. Normally, the name of
a struct will be automatically generated in its plural form. For example if
your struct has the type Post, its generated name is posts. And the url
for the GET request for post with ID 1 would be /posts/1.
If you implement the GetName() method and it returns special-posts, then
this would be the name in the type field of the generated json and also the
name for the generated routes.
Currently, you must implement this interface, if you have a struct type that
consists of multiple words and you want to use a hyphenized name. For example UnicornPost.
Our default Jsonifier would then generate the name unicornPosts. But if you
want the recommended name, you
have to implement GetName
func (s UnicornPost) GetName() string {
return "unicorn-posts"
}type MarshalIdentifier interface {
GetID() string
}Implement this interface to marshal a struct.
type UnmarshalIdentifier interface {
SetID(string) error
}This is the corresponding interface to MarshalIdentifier. Implement this interface in order to unmarshal incoming json into a struct.
For relationships to work, there are 3 Interfaces that you can use:
type MarshalReferences interface {
GetReferences() []Reference
}
// MarshalLinkedRelations must be implemented if there are references and the reference IDs should be included
type MarshalLinkedRelations interface {
MarshalReferences
MarshalIdentifier
GetReferencedIDs() []ReferenceID
}
// MarshalIncludedRelations must be implemented if referenced structs should be included
type MarshalIncludedRelations interface {
MarshalReferences
MarshalIdentifier
GetReferencedStructs() []MarshalIdentifier
}Implementing those interfaces is not mandatory and depends on your use cases. If your API has any relationships,
you must at least implement MarshalReferences and MarshalLinkedRelations.
MarshalReferences must be implemented in order for api2go to know which relations are possible for your struct.
MarshalLinkedRelations must be implemented to retrieve the IDs of the relations that are connected to this struct. This method
could also return an empty array, if there are currently no relations. This is why there is the MarshalReferences interface, so that api2go
knows what is possible, even if nothing is referenced at the time.
In addition to that, you can implement MarshalIncludedRelations which exports the complete referenced structs and embeds them in the json
result inside the included object.
That way you can choose how you internally manage relations. So, there are no limits regarding the use of ORMs.
Incoming jsons can also contain reference IDs. In order to unmarshal them correctly, you have to implement the following interfaces. If you only have to-one
relationships, the UnmarshalToOneRelations interface is enough.
// UnmarshalToOneRelations must be implemented to unmarshal to-one relations
type UnmarshalToOneRelations interface {
SetToOneReferenceID(name, ID string) error
}
// UnmarshalToManyRelations must be implemented to unmarshal to-many relations
type UnmarshalToManyRelations interface {
SetToManyReferenceIDs(name string, IDs []string) error
}If you need to know more about how to use the interfaces, look at our tests or at the example project.
Please keep in mind that this only works if you implemented the previously mentioned interfaces. Manual marshalling and
unmarshalling makes sense, if you do not want to use our API that automatically generates all the necessary routes for you. You
can directly use our sub-package github.com/manyminds/api2go/jsonapi
comment1 = Comment{ID: 1, Text: "First!"}
comment2 = Comment{ID: 2, Text: "Second!"}
post = Post{ID: 1, Title: "Foobar", Comments: []Comment{comment1, comment2}}
json, err := jsonapi.Marshal(post)will yield
…You can also use jsonapi.MarshalWithURLs to automatically generate URLs for the rest endpoints that have a
version and BaseURL prefix. This will generate the same routes that our API uses. This adds self and related fields
for relations inside the relationships object.
Recover the structure from above using. Keep in mind that Unmarshalling with included structs does not work yet. So Api2go cannot be used as a client yet.
var posts []Post
err := jsonapi.Unmarshal(json, &posts)
// posts[0] == Post{ID: 1, Title: "Foobar", CommentsIDs: []int{1, 2}}When using a SQL Database it is most likely you want to use the special SQL-Types from the database/sql package. These are
The Problem is, that they internally manage the null value behavior by using a custom struct. In order to Marshal and Unmarshal
these values, it is required to implement the json.Marshaller and json.Unmarshaller interfaces of the go standard library.
But you dont have to do this by yourself! There already is a library that did the work for you. We recommend that you use the types of this library: http://gopkg.in/guregu/null.v3/zero
In order to use omitempty with those types, you need to specify them as pointers in your struct.
If you want to use api2go with gin you need to use a different router than the default one. Get the according adapter using:
go get -tags=gingonic github.com/manyminds/api2go
Currently the supported tags are: gingonic,gorillamux, or echo.
After that you can bootstrap api2go the following way:
…Keep in mind that you absolutely should map api2go under its own namespace to not get conflicts with your normal routes.
If you need api2go with any different go framework, just send a PR with the according adapter :-)
First, write an implementation of either api2go.ResourceGetter, api2go.ResourceCreator, api2go.ResourceUpdater, api2go.ResourceDeleter, or any combination of them.
You can also write an implementation the CRUD interface which embed all of them.
You have to implement at least one of these 4 methods:
…If you want to return a jsonapi compatible error because something went wrong inside the CRUD methods, you can use our
HTTPError struct, which can be created with NewHTTPError. This allows you to set the error status code and add
as many information about the error as you like. See: jsonapi error
To fetch all objects of a specific resource you can choose to implement one or both of the following interfaces:
type FindAll interface {
// FindAll returns all objects
FindAll(req Request) (Responder, error)
}
type PaginatedFindAll interface {
PaginatedFindAll(req Request) (totalCount uint, response Responder, err error)
}FindAll returns everything. You could limit the results only by using Query Params which are described here
PaginatedFindAll can also use Query Params, but in addition to that it does not need to send all
No open issues yet, or sync has not completed.