jsonapi

package module
v0.0.0-...-4fc9bfe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2016 License: MIT Imports: 2 Imported by: 0

README

jsonapi

GoDoc Build Status codecov

Package jsonapi converts Go data to and from the JSON API format.

go get -u github.com/smotes/jsonapi

Overview

The JSON API specification has strict requirements on the structure of any JSON request/response containing data, a structure most likely varying drastically from any backend data structures. This package solely aims to provide utilities around converting said backend data to the specification's while avoiding reflection and assuming nothing about the JSON encoding/decoding package used.

Avoid reflection

Implement multiple (mostly optional) interfaces to convert your data to and from JSON API documents and resources.

type Person struct {
	ID   int
	Name string
	Age  int
}

func (p *Person) GetID() (string, error) {
	return strconv.Itoa(p.ID), nil
}

func (p *Person) GetType() (string, error) {
	return "people", nil
}
Use any JSON package

This package converts your data to and from a common Resource struct, which is compatible with any third-party JSON package with the "encoding/json" API, or can work with byte slices.

var person = &Person{ID: 1, Name: "John", Age: 42}

resource, err := jsonapi.ToResource(&person, false)
handle(err)

b, err := json.Marshal(&resource)
handle(err)

fmt.Println(string(b))
// {"id": "1", "type": "people"}
Tested examples

Tested, detailed examples are included on the ToResource and FromResource functions in the godocs.

Contributing

  • Fork the repository.
  • Code your changes.
  • If applicable, write tests and documentation for the new functionality (please ensure all tests pass, have 100% coverage and pass go vet and golint).
  • Raise a new pull request with a short description.

Documentation

Overview

Package jsonapi provides utilities for converting Go data to/from the JSON API format detailed at http://jsonapi.org.

Consider an example, some structs representing a simple backend for a blog engine:

type Person {
	ID   int
	Name string
	Age  int
}

type Article struct {
	ID      int
	Title   string
	Body    string
	Created time.Time
	Updated time.Time
	Author  *Person
}

The article data could be represented in a JSON format in compliance with the JSON API specification like so:

{
	"id": "...",
	"type": "articles",
	"attributes": {
		"title": "...",
		"body": "...",
		"created": "...",
		"updated": "..."
	},
	"relationships": {
		"author": { ... }
	},
	"links": { ... },
	"meta": { ... }
}

In order to expose the article's data in the desired format, the some methods must be implemented to satisfy the expected interfaces for converting to/from resources. For a full list of supported interfaces as well as tested examples, please see the documentation for ToResource and FromResource.

Index

Examples

Constants

View Source
const MediaType string = "application/vnd.api+json"

MediaType is the required media type for the exchange of data by the JSON API specification.

Variables

This section is empty.

Functions

func FromResource

func FromResource(adapter interface{}, r *Resource, full bool) error

FromResource uses the adapter implementation v to set the values from the corresponding Resource r.

Several adapter interfaces are used to convert a Resource to a custom type; one is required and the rest are optional.

type identityWriteAdapter interface {
	SetID(string) error
	SetType(string) error
}

The identity write adapter is used to populate the ID field on custom type from the Resource. It is required that all custom types implement this interface or FromResource will return an error.

type attributesWriteAdapter interface {
	SetAttributes(map[string]interface{}) error
}

The attributes write adapter is used to populate fields on the custom type from the Attributes on the Resource. It is optional and will be ignored if not implemented, or if it returns nil.

type relationshipsWriteAdapter interface {
	SetRelationships(jsonapi.Relationships) error
}

The relationships write adapter is used to populate fields on the custom type from the Relationships on the Resource. It is optional and will be ignored if not implemented, or if it returns nil.

Note the lack of a linksWriteAdapter and metaWriteAdapter, or a SetType method on the identityWriterAdapter. As per the JSON API specification for client interaction with an API (including reading, creating, updating or deleting any resources or relationships), these adapters should be unnecessary.

Example

This example includes an integration test with jsonapi.FromResource function and the json.Unmarshal function from the "encoding/json" package.

testArticleJSON = `
	{
		"id": "1",
		"type": "articles",
		"attributes": {
			"title": "JSON API paints my bikeshed!",
			"body": "The shortest article. Ever."
		},
		"relationships": {
			"author": {
				"data": {
					"id": "42",
					"type": "people"
				},
				"links": {
					"self": "http://example.com/articles/1/relationships/author",
					"related": "http://example.com/articles/1/author"
				}
			}
		},
		"links": {
			"self": "http://example.com/articles/1"
		},
		"meta": {
			"total": 42,
			"type": "foo"
		}
	}
	`

p := Person{}
article := Article{}
articleResource := jsonapi.Resource{}
personResource := jsonapi.Resource{}

// first, unmarshal and convert article resource to article
if err := json.Unmarshal([]byte(testArticleJSON), &articleResource); err != nil {
	panic(err)
}
if err := jsonapi.FromResource(&article, &articleResource, true); err != nil {
	panic(err)
}

// person resource in author relationship must be unmarshaled separately afterwards
if rel, ok := articleResource.Relationships.Get("author"); ok {
	if err := json.Unmarshal(rel.Data, &personResource); err != nil {
		panic(err)
	}
	if err := jsonapi.FromResource(&p, &personResource, true); err != nil {
		panic(err)
	}
}
article.Author = &p

fmt.Println(article.ID, article.Title, article.Author.ID)
Output:

1 JSON API paints my bikeshed! 42

Types

type Document

type Document struct {
	Data     json.RawMessage        `json:"data,omitempty"`
	Errors   []Error                `json:"errors,omitempty"`
	Meta     map[string]interface{} `json:"meta,omitempty"`
	Info     *Info                  `json:"jsonapi,omitempty"`
	Links    Links                  `json:"links,omitempty"`
	Included []Resource             `json:"included,omitempty"`
}

Document represents top-level document at the root of any JSON API request/response containing data.

http://jsonapi.org/format/#document-top-level

type Error

type Error struct {
	ID     string                 `json:"id,omitempty"`
	Status string                 `json:"status,omitempty"`
	Code   string                 `json:"code,omitempty"`
	Title  string                 `json:"title,omitempty"`
	Detail string                 `json:"detail,omitempty"`
	Links  Links                  `json:"links,omitempty"`
	Meta   map[string]interface{} `json:"meta,omitempty"`
}

Error represents a JSON API error object.

http://jsonapi.org/format/#error-objects

type Info

type Info struct {
	Version string                 `json:"version"`
	Meta    map[string]interface{} `json:"meta,omitempty"`
}

Info represents a JSON API Object, used as the "jsonapi" member in the top-level document, which provides information about its implementation.

http://jsonapi.org/format/#document-jsonapi-object

type Link struct {
	Href string                 `json:"href"`
	Meta map[string]interface{} `json:"meta,omitempty"`
}

Link represents a JSON API link object, which includes the required "href" key and optional "meta" key.

http://jsonapi.org/format/#document-links

type Links map[string]interface{}

Links represents a JSON API links object, which can include JSON API link objects or strings representing links.

http://jsonapi.org/format/#document-links

func (Links) Add

func (ls Links) Add(key string, v *Link)

Add adds the object key, value pair to the meta object. It overwrites any existing values associated with key.

func (Links) AddString

func (ls Links) AddString(key, v string)

AddString adds the string key, value pair to the meta object. It overwrites any existing values associated with key.

func (Links) Delete

func (ls Links) Delete(key string)

Delete deletes the value associated with the given key.

func (Links) Get

func (ls Links) Get(key string) (*Link, bool)

Get returns the object value associated with the given key and an existence check. Returns a nil/false if the value associated with the key does not exist or does not reference a Link struct.

func (Links) GetString

func (ls Links) GetString(key string) (string, bool)

GetString returns the string value associated with the given key and an existence check. Returns an empty string/false if the value associated with the key does not exist or the value is not of type string.

type Relationship

type Relationship struct {
	Links Links                  `json:"links,omitempty"`
	Data  json.RawMessage        `json:"data,omitempty"`
	Meta  map[string]interface{} `json:"meta,omitempty"`
}

Relationship represents a member of the JSON API relationships object.

type Relationships

type Relationships map[string]*Relationship

Relationships represents a JSON API relationships object

http://jsonapi.org/format/#document-resource-object-relationships

func (Relationships) Add

func (rs Relationships) Add(key string, r *Relationship)

Add adds the key, value pair to the meta object. It overwrites any existing values associated with key.

func (Relationships) Delete

func (rs Relationships) Delete(key string)

Delete deletes the value associated with the given key.

func (Relationships) Get

func (rs Relationships) Get(key string) (*Relationship, bool)

Get returns the value associated with the given key and an existence check.

type Resource

type Resource struct {
	ID            string                 `json:"id"`
	Type          string                 `json:"type"`
	Attributes    map[string]interface{} `json:"attributes,omitempty"`
	Relationships Relationships          `json:"relationships,omitempty"`
	Links         Links                  `json:"links,omitempty"`
	Meta          map[string]interface{} `json:"meta,omitempty"`
}

Resource represents a JSON API resource identifier or resource object. Each valid JSON API resource object must contain at least the "id" and "type" keys, and may contain the optional "attributes", "relationships", "links" and "meta" keys.

Note that any of the optional keys will be omitted if a value is not provided.

For more information, see the specification at:

http://jsonapi.org/format/#document-resource-object

http://jsonapi.org/format/#document-resource-identifier-objects

func ToResource

func ToResource(v interface{}, full bool) (*Resource, error)

ToResource uses the adapter implementation v to return the corresponding Resource.

If full is true, ToResource will attempt to set the resource object's optional members including Attributes, Links and Meta. If full is false, the resultant resource object will only include the ID and Type members, allowing it to be used as a resource identifier object.

Several adapter interfaces are used to convert a custom type to a Resource; one is required and the rest are optional.

type identityReadAdapter interface {
	GetID() (string, error)
	GetType() (string, error)
}

The identity read adapter is used to populate the ID and Type fields on the resultant Resource. It is required that all custom types implement this interface or ToResource will return an error.

type attributesReadAdapter interface {
	GetAttributes() (map[string]interface{}, error)
}

The attributes read adapter is used to populate the Attributes field on the resultant Resource. It is optional and will be ignored if not implemented, or if it returns nil.

type relationshipsReadAdapter interface {
	GetRelationships() (jsonapi.Relationships, error)
}

The relationships read adapter is used to populate the Relationships field on the resultant Resource. It is optional and will be ignored if not implemented, or if it returns nil.

type linksReadAdapter interface {
	GetLinks() (jsonapi.Links, error)
}

The links read adapter is used to populate the Links field on the resultant Resource. It is optional and will be ignored if not implemented, or if it returns nil.

type metaReadAdapter interface {
	GetMeta() (map[string]interface{}, error)
}

The meta read adapter is used to populate the Meta field on the resultant Resource. It is optional and will be ignored if not implemented, or if it returns nil.

Example

This example includes an integration test with jsonapi.ToResource function and the json.Marshal function from the "encoding/json" package.

article := Article{
	ID:    4,
	Title: "some title",
	Body:  "some body",
	Author: &Person{
		ID:   1,
		Name: "John",
		Age:  42,
	},
}

// convert article to resource object
resource, err := jsonapi.ToResource(&article, true)
if err != nil {
	panic(err)
}

// then marshal resource using "encoding/json" package
b, err := json.Marshal(&resource)
if err != nil {
	panic(err)
}

fmt.Println(string(b))
Output:

{"id":"4","type":"articles","attributes":{"body":"some body","title":"some title"},"relationships":{"author":{"links":{"related":"http://example.com/articles/1/author","self":"http://example.com/articles/1/relationships/author"},"data":{"id":"1","type":"people"}}},"links":{"self":"http://example.com/articles/1"},"meta":{"total":42,"type":"foo"}}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL