jsonapi

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2023 License: MIT Imports: 3 Imported by: 7

README

jsonapi-go

Go jsonapi client

Go Report Card GoDoc

Lightweight JSON API implementation for Go.

Installing

go get -u "github.com/keygen-sh/jsonapi-go"

Running the tests

Go to jsonapi-go package directory and run:

go test

Usage
Marshal

For instance we have Go program what implements a simple library, we have Book and Author structs:

type Book struct {
	ID              string
	AuthorID        string
	Title           string
	PublicationDate time.Time
}

type Author struct {
	ID        string
	FirstName string
	LastName  string
}

We want to produce the JSON representation of the data. Let's modify the struct by adding json tags to it and implement functions GetID and GetType as required for MarshalResourceIdentifier interface, one more function GetData required for MarshalData interface.

type Book struct {
	ID              string    `json:"-"`
	AuthorID        string    `json:"-"`
	Title           string    `json:"title"`
	PublicationDate time.Time `json:"publication_date"`
}

func (b Book) GetID() string {
	return b.ID
}

func (b Book) GetType() string {
	return "books"
}

func (b Book) GetData() interface{} {
	return b
}

type Author struct {
	ID        string `json:"-"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

func (a Author) GetID() string {
	return a.ID
}

func (a Author) GetType() string {
	return "authors"
}

func (a Author) GetData() interface{} {
	return a
}

By running Marshal function for Book and Author the output will be a []byte of json data.

Initial data:

alan := Author{
	ID:        "1",
	FirstName: "Alan A. A.",
	LastName:  "Donovan",
}

publicationDate, _ := time.Parse(time.RFC3339, "2015-01-01T00:00:00Z")

book := Book{
	ID:              "1",
	Title:           "Go Programming Language",
	AuthorID:        alan.ID,
	PublicationDate: publicationDate,
}

Running Marshal:

bookJSON, _ := jsonapi.Marshal(book1)
authorJSON, _ := jsonapi.Marshal(alan)

Output:

{
	"data": {
		"type": "books",
		"id": "1",
		"attributes": {
			"title": "Go Programming Language",
			"publication_date": "2015-01-01T00:00:00Z"
		}
	}
}

and

{
	"data": {
		"type": "authors",
		"id": "1",
		"attributes": {
			"first_name": "Alan A. A.",
			"last_name": "Donovan"
		}
	}
}
Relationships

Add relationships to the resource is easy to do by implementing MarshalRelationships interface e.g. for Book we will add GetRelationships function.

func (b Book) GetRelationships() map[string]interface{} {
	relationships := make(map[string]interface{})

	relationships["author"] = jsonapi.ResourceObjectIdentifier{
		ID:   b.AuthorID,
		Type: "authors",
	}

	return relationships
}

The Marshal output will be:

{
	"data": {
		"type": "books",
		"id": "1",
		"attributes": {
			"title": "Go Programming Language",
			"publication_date": "2015-01-01T00:00:00Z"
		},
		"relationships": {
			"author": {
				"data": {
					"type": "authors",
					"id": "1"
				}
			}
		}
	}
}
Included

Adding to JSON document included is easy by adding function GetIncluded it will implement MarshalIncluded interface.

func (b Book) GetIncluded() []interface{} {
	var included []interface{}

  //`authors` a global array with all authors, but it can be a DB or something else
	for _, author := range authors {
		if author.ID == b.AuthorID {
			included = append(included, author)
		}
	}

	return included
}

When JSON document is a collection of resources better way to implement additional data type and Books method GetIncluded, otherwise all same included resources may be included several times.

type Books []Book

func (b Books) GetIncluded() []interface{} {
  //do something
}

Book with included will be looks like:

{
	"data": {
		"type": "books",
		"id": "1",
		"attributes": {
			"title": "Go Programming Language",
			"publication_date": "2015-01-01T00:00:00Z"
		},
		"relationships": {
			"author": {
				"data": {
					"type": "authors",
					"id": "1"
				}
			}
		}
	},
	"included": [{
		"type": "authors",
		"id": "1",
		"attributes": {
			"first_name": "Alan A. A.",
			"last_name": "Donovan"
		}
	}]
}
Meta

Book's GetMeta will implement MarshalMeta interface. meta section will be added to json document.

func (b Book) GetMeta() interface{} {
	return Meta{ ReadCount: 42 }
}

the output:

{
	"data": {
		"type": "books",
		"id": "1",
		"attributes": {
			"title": "Go Programming Language",
			"publication_date": "2015-01-01T00:00:00Z"
		},
		"meta": {
			"read_count": 42
		},
		"relationships": {
			"author": {
				"data": {
					"type": "authors",
					"id": "1"
				}
			}
		}
	},
	"included": [{
		"type": "authors",
		"id": "1",
		"attributes": {
			"first_name": "Alan A. A.",
			"last_name": "Donovan"
		}
	}],
	"meta": {
		"read_count": 42
	}
}

Here we can find that meta includes in resource object and into JSON document. It happened, because resource and document may have their own meta, to avoid such behavior we should implement Book document data type and implement GetMeta for the Book's document.

type BookDocumentMeta struct {
	TotalCount int `json:"total_count"`
}

type BookDocument struct {
  Data Book
  Meta BookDocumentMeta
}

func (b BookDocument) GetData() interface{} {
	return b.Data
}

func (b BookDocument) GetMeta() interface{} {
	return b.Meta
}

...

bookJSON, _ := jsonapi.Marshal(BookDocument{
  Data: book, //book from previous examples
  Meta: BookDocumentMeta{ 17 }, //let's imagine that we have 17 books in our library
})

Output:

{
	"data": {
		"type": "books",
		"id": "1",
		"attributes": {
			"title": "Go Programming Language",
			"publication_date": "2015-01-01T00:00:00Z"
		},
		"meta": {
			"read_count": 42
		},
		"relationships": {
			"author": {
				"data": {
					"type": "authors",
					"id": "1"
				}
			}
		}
	},
	"meta": {
		"total_count": 17
	}
}
Unmarshal

Now we will implement the examples, how to put values from JSON document into Go struct. We will use the same data types Book and Author

But the set of functions will be different. Book:

type Book struct {
	ID              string    `json:"-"`
	Type            string    `json:"-"`
	AuthorID        string    `json:"-"`
	Title           string    `json:"title"`
	PublicationDate time.Time `json:"publication_date"`
}

func (b *Book) SetID(id string) error {
	b.ID = id
	return nil
}

func (b *Book) SetType(t string) error {
	b.Type = t
	return nil
}

func (b *Book) SetData(to func(target interface{}) error) error {
	return to(b)
}

Here is JSON data:

{
  	"data": {
  		"type": "books",
  		"id": "1",
  		"attributes": {
  			"title": "Go Programming Language",
  			"publication_date": "2015-01-01T00:00:00Z"
  		},
      "relationships": {
        "author": {
          "data": {
            "type": "authors",
            "id": "1"
          }
        }
      }
  	}
  }

Let's call Unmarshal function.

  book := Book{}
	jsonapi.Unmarshal(bookJSON, &book) //bookJSON is []byte of JSON data.

At the end we will have Go struct:

_ = Book{
  ID:	      "1",
  Type:	    "books",
  AuthorID:	"",
  Title:	   "Go Programming Language"
  PublicationDate:	2015-01-01 00:00:00 +0000 UTC,
}

But the AuthorID is empty, to set this relationship we should implement UnmarshalRelationships interface, by creating function SetRelationships:

func (b *Book) SetRelationships(relationships map[string]interface{}) error {
	if relationship, ok := relationships["author"]; ok {
		b.AuthorID = relationship.(*jsonapi.ResourceObjectIdentifier).ID
	}

	return nil
}

call Unmarshal again and look at the result.

Collection unmarshal

When you need to unmarshal collections, you should implement SetData function for collection datatype.

type Books []Book

func (b *Books) SetData(to func(target interface{}) error) error {
	return to(b)
}

If resource Book data type have SetRelationships function, in the collection all relationships will be filled. Example:

var booksJSON = []byte(`
  {
  	"data":
    [
      {
    		"type": "books",
    		"id": "1",
    		"attributes": {
    			"title": "Go Programming Language",
    			"publication_date": "2015-01-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "1"
    				}
    			}
    		}
    	},
      {
    		"type": "books",
    		"id": "2",
    		"attributes": {
    			"title": "Learning Functional Programming in Go",
    			"publication_date": "2017-11-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "2"
    				}
    			}
    		}
    	},
      {
    		"type": "books",
    		"id": "3",
    		"attributes": {
    			"title": "Go in Action",
    			"publication_date": "2015-11-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "3"
    				}
    			}
    		}
    	}
    ]
  }
`)

books := Books{}
jsonapi.Unmarshal(booksJSON, &books)

The output:

_ = Books{
  {
    ID:	"1",
    Type:	"books",
    AuthorID:	"1",
    Title:	"Go Programming Language",
    PublicationDate:	2015-01-01 00:00:00 +0000 UTC,
  },
  {
    ID:	"2",
    Type:	"books",
    AuthorID:	"2",
    Title:	"Learning Functional Programming in Go",
    PublicationDate:	2017-11-01 00:00:00 +0000 UTC,
  },
  {
    ID:	"3",
    Type:	"books",
    AuthorID:	"3",
    Title:	"Go in Action",
    PublicationDate:	2015-11-01 00:00:00 +0000 UTC,
  },
}
Example Library client and server applications.

server.go

package main

import (
	"github.com/keygen-sh/jsonapi-go"
	"log"
	"net/http"
	"time"
)

type Meta struct {
	Count int `json:"count"`
}

type Book struct {
	ID              string    `json:"-"`
	AuthorID        string    `json:"-"`
	Title           string    `json:"title"`
	PublicationDate time.Time `json:"publication_date"`
}

func (b Book) GetID() string {
	return b.ID
}

func (b Book) GetType() string {
	return "books"
}

func (b Book) GetData() interface{} {
	return b
}

func (b Book) GetRelationships() map[string]interface{} {
	relationships := make(map[string]interface{})

	relationships["author"] = jsonapi.ResourceObjectIdentifier{
		ID:   b.AuthorID,
		Type: "authors",
	}

	return relationships
}

func (b Book) GetIncluded() []interface{} {
	var included []interface{}

	for _, author := range authors {
		if author.ID == b.AuthorID {
			included = append(included, author)
		}
	}

	return included
}

type Books []Book

func (b Books) GetData() interface{} {
	return b
}

func (b Books) GetMeta() interface{} {
	return Meta{Count: len(books)}
}

func (b Books) GetIncluded() []interface{} {
	var included []interface{}

	authorsMap := make(map[string]Author)

	for _, book := range b {
		for _, author := range authors {
			if book.AuthorID == author.ID {
				authorsMap[author.ID] = author
			}
		}
	}

	for _, author := range authorsMap {
		included = append(included, author)
	}

	return included
}

type Author struct {
	ID        string `json:"-"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

func (a Author) GetID() string {
	return a.ID
}

func (a Author) GetType() string {
	return "authors"
}

func (a Author) GetData() interface{} {
	return a
}

func (a Author) GetIncluded() []interface{} {
	var included []interface{}

	for _, book := range books {
		if book.AuthorID == a.ID {
			included = append(included, book)
		}
	}

	return included
}

type Authors []Author

func (a Authors) GetMeta() interface{} {
	return Meta{Count: len(authors)}
}

func (a Authors) GetData() interface{} {
	return a
}

func (a Authors) GetIncluded() []interface{} {
	var included []interface{}

	booksMap := make(map[string]Book)

	for _, author := range a {
		for _, book := range books {
			if book.AuthorID == author.ID {
				booksMap[book.ID] = book
			}
		}
	}

	for _, book := range booksMap {
		included = append(included, book)
	}

	return included
}

var (
	authors Authors
	books   Books
)

func bookHandler(w http.ResponseWriter, req *http.Request) {

	id := req.URL.Path[len("/books/"):]

	for _, book := range books {
		if book.ID == id {
			bookData, err := jsonapi.Marshal(book)

			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			w.Header().Set("Content-Type", "application/vnd.api+json")
			w.Write(bookData)
			w.WriteHeader(http.StatusOK)
			return
		}
	}

	w.WriteHeader(http.StatusNotFound)
}

func booksHandler(w http.ResponseWriter, req *http.Request) {

	booksData, err := jsonapi.Marshal(books)

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.api+json")
	w.Write(booksData)
	w.WriteHeader(http.StatusOK)
}

func authorHandler(w http.ResponseWriter, req *http.Request) {

	id := req.URL.Path[len("/authors/"):]

	for _, author := range authors {
		if author.ID == id {
			authorData, err := jsonapi.Marshal(author)

			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}

			w.Header().Set("Content-Type", "application/vnd.api+json")
			w.Write(authorData)
			w.WriteHeader(http.StatusOK)
			return
		}
	}

	w.WriteHeader(http.StatusNotFound)
}

func authorsHandler(w http.ResponseWriter, req *http.Request) {

	authorsData, err := jsonapi.Marshal(authors)

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/vnd.api+json")
	w.Write(authorsData)
	w.WriteHeader(http.StatusOK)
}

func main() {
	var publicationDate time.Time

	alan := Author{ID: "1", FirstName: "Alan A. A.", LastName: "Donovan"}
	authors = append(authors, alan)

	lex := Author{ID: "2", FirstName: "Lex", LastName: "Sheehan"}
	authors = append(authors, lex)

	william := Author{ID: "3", FirstName: "William", LastName: "Kennedy"}
	authors = append(authors, william)

	publicationDate, _ = time.Parse(time.RFC3339, "2015-01-01T00:00:00Z")

	book1 := Book{
		ID:              "1",
		Title:           "Go Programming Language",
		AuthorID:        alan.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book1)

	publicationDate, _ = time.Parse(time.RFC3339, "2017-11-01T00:00:00Z")

	book2 := Book{
		ID:              "2",
		Title:           "Learning Functional Programming in Go",
		AuthorID:        lex.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book2)

	publicationDate, _ = time.Parse(time.RFC3339, "2015-11-01T00:00:00Z")

	book3 := Book{
		ID:              "3",
		Title:           "Go in Action",
		AuthorID:        william.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book3)

	http.HandleFunc("/books/", bookHandler)
	http.HandleFunc("/books", booksHandler)
	http.HandleFunc("/authors/", authorHandler)
	http.HandleFunc("/authors", authorsHandler)

	log.Fatal(http.ListenAndServe(":8080", nil))
}

client.go

package main

import  (
  "fmt"
	"io/ioutil"
	"log"
	"net/http"
  "time"
  "github.com/keygen-sh/jsonapi-go"
)

type Book struct {
  ID       string `json:"-"`
  Type     string `json:"-"`
  AuthorID string `json:"-"`
  Title    string `json:"title"`
  PublicationDate time.Time `json:"publication_date"`
}

func(b *Book) SetID(id string) error {
  b.ID = id
  return nil
}

func(b *Book) SetType(t string) error {
  b.Type = t
  return nil
}

func(b *Book) SetData(to func(target interface{}) error) error {
  return to(b)
}

func(b *Book) SetRelationships(relationships map[string]interface{}) error {
  if relationship, ok := relationships["author"]; ok {
    b.AuthorID = relationship.(*jsonapi.ResourceObjectIdentifier).ID
  }

  return nil
}

type Books []Book

func(b *Books) SetData(to func(target interface{}) error) error {
  return to(b)
}

type Author struct {
  ID string `json:"-"`
  Type string `json:"-"`
  FirstName string `json:"first_name"`
  LastName string `json:"last_name"`
}

func(a *Author) SetID(id string) error {
  a.ID = id
  return nil
}

func(a *Author) SetType(t string) error {
  a.Type = t
  return nil
}

func(a *Author) SetData(to func(target interface{}) error) error {
  return to(a)
}

type Authors []Author

func(a *Authors) SetData(to func(target interface{}) error) error {
  return to(a)
}

func printBook(b Book) {
  fmt.Printf("ID:\t%v,\nType:\t%v\nAuthorID:\t%v\nTitle:\t%v\nPublicationDate:\t%v\n", b.ID, b.Type, b.AuthorID, b.Title, b.PublicationDate)
}

func printAuthor(a Author) {
  fmt.Printf("ID:\t%v,\nType:\t%v\nFirstName:\t%v\nLastName:\t%v\n", a.ID, a.Type, a.FirstName, a.LastName)
}

func GetBooks() (books Books){

    res, err := http.Get("http://localhost:8080/books")

  	if err != nil {
  		log.Fatal(err)
  	}

    if res.StatusCode != http.StatusOK {
      return
    }

  	booksJSON, err := ioutil.ReadAll(res.Body)
  	res.Body.Close()

    if err != nil {
  		log.Fatal(err)
  	}
    _, jsonapiErr := jsonapi.Unmarshal(booksJSON, &books)

    if jsonapiErr != nil {
      log.Fatal(jsonapiErr)
    }

    return books
}

func GetAuthors() (authors Authors){

    res, err := http.Get("http://localhost:8080/authors")

  	if err != nil {
  		log.Fatal(err)
  	}

    if res.StatusCode != http.StatusOK {
      return
    }

  	authorsJSON, err := ioutil.ReadAll(res.Body)
  	res.Body.Close()

    if err != nil {
  		log.Fatal(err)
  	}
    _, jsonapiErr := jsonapi.Unmarshal(authorsJSON, &authors)

    if jsonapiErr != nil {
      log.Fatal(jsonapiErr)
    }

    return authors
}

func main() {
  books := GetBooks()

  for _, book := range books {
    printBook(book)
  }

  authors := GetAuthors()

  for _, author := range authors {
    printAuthor(author)
  }
}
See also

Documentation

Index

Examples

Constants

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

ContentType describes data content type.

Variables

This section is empty.

Functions

func Marshal

func Marshal(payload interface{}) ([]byte, error)

Marshal serialize Go struct into []byte JSON API document If the corresponding interfaces are implemented the output will contain, relationships, included, meta and errors.

Example
package main

import (
	"fmt"
	"time"

	"github.com/keygen-sh/jsonapi-go"
)

type TestMarshalMeta struct {
	Count int `json:"count"`
}

type MarshalBook struct {
	ID              string    `json:"-"`
	AuthorID        string    `json:"-"`
	Title           string    `json:"title"`
	PublicationDate time.Time `json:"publication_date"`
}

func (b MarshalBook) GetID() string {
	return b.ID
}

func (b MarshalBook) GetType() string {
	return "books"
}

func (b MarshalBook) GetData() interface{} {
	return b
}

func (b MarshalBook) GetRelationships() map[string]interface{} {
	relationships := make(map[string]interface{})

	relationships["author"] = jsonapi.ResourceObjectIdentifier{
		ID:   b.AuthorID,
		Type: "authors",
	}

	return relationships
}

func (b MarshalBook) GetIncluded() []interface{} {
	var included []interface{}

	for _, author := range authors {
		if author.ID == b.AuthorID {
			included = append(included, author)
		}
	}

	return included
}

type MarshalBooks []MarshalBook

func (b MarshalBooks) GetData() interface{} {
	return b
}

func (b MarshalBooks) GetMeta() interface{} {
	return TestMarshalMeta{Count: len(books)}
}

func (b MarshalBooks) GetIncluded() []interface{} {
	var included []interface{}

	authorsMap := make(map[string]MarshalAuthor)

	for _, book := range b {
		for _, author := range authors {
			if book.AuthorID == author.ID {
				authorsMap[author.ID] = author
			}
		}
	}

	for _, author := range authorsMap {
		included = append(included, author)
	}

	return included
}

type MarshalAuthor struct {
	ID        string `json:"-"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

func (a MarshalAuthor) GetID() string {
	return a.ID
}

func (a MarshalAuthor) GetType() string {
	return "authors"
}

func (a MarshalAuthor) GetData() interface{} {
	return a
}

func (a MarshalAuthor) GetIncluded() []interface{} {
	var included []interface{}

	for _, book := range books {
		if book.AuthorID == a.ID {
			included = append(included, book)
		}
	}

	return included
}

type MarshalAuthors []MarshalAuthor

func (a MarshalAuthors) GetMeta() interface{} {
	return TestMarshalMeta{Count: len(authors)}
}

func (a MarshalAuthors) GetData() interface{} {
	return a
}

func (a MarshalAuthors) GetIncluded() []interface{} {
	var included []interface{}

	booksMap := make(map[string]MarshalBook)

	for _, author := range a {
		for _, book := range books {
			if book.AuthorID == author.ID {
				booksMap[book.ID] = book
			}
		}
	}

	for _, book := range booksMap {
		included = append(included, book)
	}

	return included
}

var (
	authors MarshalAuthors
	books   MarshalBooks
)

func main() {
	var publicationDate time.Time

	alan := MarshalAuthor{
		ID:        "1",
		FirstName: "Alan A. A.",
		LastName:  "Donovan",
	}
	authors = append(authors, alan)

	lex := MarshalAuthor{
		ID:        "2",
		FirstName: "Lex",
		LastName:  "Sheehan",
	}
	authors = append(authors, lex)

	william := MarshalAuthor{
		ID:        "3",
		FirstName: "William",
		LastName:  "Kennedy",
	}
	authors = append(authors, william)

	publicationDate, _ = time.Parse(time.RFC3339, "2015-01-01T00:00:00Z")

	book1 := MarshalBook{
		ID:              "1",
		Title:           "Go Programming Language",
		AuthorID:        alan.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book1)

	publicationDate, _ = time.Parse(time.RFC3339, "2017-11-01T00:00:00Z")

	book2 := MarshalBook{
		ID:              "2",
		Title:           "Learning Functional Programming in Go",
		AuthorID:        lex.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book2)

	publicationDate, _ = time.Parse(time.RFC3339, "2015-11-01T00:00:00Z")

	book3 := MarshalBook{
		ID:              "3",
		Title:           "Go in Action",
		AuthorID:        william.ID,
		PublicationDate: publicationDate,
	}
	books = append(books, book3)

	bookJSON, _ := jsonapi.Marshal(book1)
	fmt.Printf("book JSON:\n%v\n", string(bookJSON))
	booksJSON, _ := jsonapi.Marshal(books)
	fmt.Printf("books JSON:\n%v\n", string(booksJSON))
	authorJSON, _ := jsonapi.Marshal(alan)
	fmt.Printf("author JSON:\n%v\n", string(authorJSON))
	authorsJSON, _ := jsonapi.Marshal(authors)
	fmt.Printf("authors JSON:\n%v\n", string(authorsJSON))
}

Types

type Document

type Document struct {
	// Document data
	Data *documentData `json:"data,omitempty"`
	// Document errors
	Errors []*ErrorObject `json:"errors,omitempty"`
	// Document included
	Included []*ResourceObject `json:"included,omitempty"`
	// Document meta
	Meta json.RawMessage `json:"meta,omitempty"`
}

Document describes Go representation of JSON API document.

func Unmarshal

func Unmarshal(data []byte, target interface{}) (*Document, error)

Unmarshal deserialize JSON API document into Gu sturct If the corresponding interfaces are implemented target will contain data from JSON API document relationships and errors.

Example
package main

import (
	"fmt"
	"time"

	"github.com/keygen-sh/jsonapi-go"
)

var bookJSON = []byte(`
  {
  	"data": {
  		"type": "books",
  		"id": "1",
  		"attributes": {
  			"title": "Go Programming Language",
  			"publication_date": "2015-01-01T00:00:00Z"
  		},
  		"relationships": {
  			"author": {
  				"data": {
  					"type": "authors",
  					"id": "1"
  				}
  			}
  		}
  	}
  }
`)

var authorJSON = []byte(`
  {
  	"data": {
  		"type": "authors",
  		"id": "1",
  		"attributes": {
  			"first_name": "Alan A. A.",
  			"last_name": "Donovan"
  		}
  	}
  }
`)

var booksJSON = []byte(`
  {
  	"data":
    [
      {
    		"type": "books",
    		"id": "1",
    		"attributes": {
    			"title": "Go Programming Language",
    			"publication_date": "2015-01-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "1"
    				}
    			}
    		}
    	},
      {
    		"type": "books",
    		"id": "2",
    		"attributes": {
    			"title": "Learning Functional Programming in Go",
    			"publication_date": "2017-11-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "2"
    				}
    			}
    		}
    	},
      {
    		"type": "books",
    		"id": "3",
    		"attributes": {
    			"title": "Go in Action",
    			"publication_date": "2015-11-01T00:00:00Z"
    		},
    		"relationships": {
    			"author": {
    				"data": {
    					"type": "authors",
    					"id": "3"
    				}
    			}
    		}
    	}
    ]
  }
`)

var authorsJSON = []byte(`
{
	"data": [
    {
  		"type": "authors",
  		"id": "1",
  		"attributes": {
  			"first_name": "Alan A. A.",
  			"last_name": "Donovan"
  		}
  	},
    {
  		"type": "authors",
  		"id": "2",
  		"attributes": {
  			"first_name": "Lex",
  			"last_name": "Sheehan"
  		}
  	},
    {
  		"type": "authors",
  		"id": "3",
  		"attributes": {
  			"first_name": "William",
  			"last_name": "Kennedy"
  		}
  	}
  ]
}
`)

type UnmarshalBook struct {
	ID              string    `json:"-"`
	Type            string    `json:"-"`
	AuthorID        string    `json:"-"`
	Title           string    `json:"title"`
	PublicationDate time.Time `json:"publication_date"`
}

func (b *UnmarshalBook) SetID(id string) error {
	b.ID = id
	return nil
}

func (b *UnmarshalBook) SetType(t string) error {
	b.Type = t
	return nil
}

func (b *UnmarshalBook) SetData(to func(target interface{}) error) error {
	return to(b)
}

func (b *UnmarshalBook) SetRelationships(relationships map[string]interface{}) error {
	if relationship, ok := relationships["author"]; ok {
		b.AuthorID = relationship.(*jsonapi.ResourceObjectIdentifier).ID
	}

	return nil
}

type UnmarshalBooks []UnmarshalBook

func (b *UnmarshalBooks) SetData(to func(target interface{}) error) error {
	return to(b)
}

type UnmarshalAuthor struct {
	ID        string `json:"-"`
	Type      string `json:"-"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

func (a *UnmarshalAuthor) SetID(id string) error {
	a.ID = id
	return nil
}

func (a *UnmarshalAuthor) SetType(t string) error {
	a.Type = t
	return nil
}

func (a *UnmarshalAuthor) SetData(to func(target interface{}) error) error {
	return to(a)
}

type UnmarshalAuthors []UnmarshalAuthor

func (a *UnmarshalAuthors) SetData(to func(target interface{}) error) error {
	return to(a)
}

func printBook(b UnmarshalBook) {
	fmt.Printf("ID:\t%v,\nType:\t%v\nAuthorID:\t%v\nTitle:\t%v\nPublicationDate:\t%v\n", b.ID, b.Type, b.AuthorID, b.Title, b.PublicationDate)
}

func printAuthor(a UnmarshalAuthor) {
	fmt.Printf("ID:\t%v,\nType:\t%v\nFirstName:\t%v\nLastName:\t%v\n", a.ID, a.Type, a.FirstName, a.LastName)
}

func main() {
	book := UnmarshalBook{}
	books := UnmarshalBooks{}
	author := UnmarshalAuthor{}
	authors := UnmarshalAuthors{}

	fmt.Printf("Book\n")
	jsonapi.Unmarshal(bookJSON, &book)
	printBook(book)

	fmt.Printf("\nBooks\n")
	jsonapi.Unmarshal(booksJSON, &books)
	for _, b := range books {
		printBook(b)
	}

	fmt.Printf("\nAuthor\n")
	jsonapi.Unmarshal(authorJSON, &author)
	printAuthor(author)

	fmt.Printf("\nAuthors\n")
	jsonapi.Unmarshal(authorsJSON, &authors)
	for _, a := range authors {
		printAuthor(a)
	}
}

type ErrorObject

type ErrorObject struct {
	// Title a short, human-readable summary of the problem.
	Title string `json:"title,omitempty"`
	// Detail is a more detailed, human-readable description of the problem.
	Detail string `json:"detail,omitempty"`
	// Code application specified value to identify the error.
	Code string `json:"code,omitempty"`
	// Source an object containing references to the source of the error.
	Source ErrorObjectSource `json:"source,omitempty"`
}

ErrorObject JSON API error object https://jsonapi.org/format/#error-objects

type ErrorObjectSource

type ErrorObjectSource struct {
	// Pointer a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute].
	Pointer string `json:"pointer,omitempty"`
}

ErrorObjectSource includes pointer ErrorObject.Source

type MarshalData

type MarshalData interface {
	GetData() interface{}
}

MarshalData interface should be implemented to be able get data from Go struct and marshal it.

GetData example:

func(s SomeStruct) GetData() interface{} {
  return s
}

type MarshalErrors

type MarshalErrors interface {
	GetErrors() []*ErrorObject
}

MarshalErrors interface should be implemented to be able marshal errors into JSON API document.

GetErrors example:

type SomeErrorType struct {
  Code string
  Title string
  Pointer string
}

type SomeErrorTypes []SomeErrorType

func(e SomeErrors) GetErrors() []*jsonapi.ErrorObject {
  var errs []*jsonapi.ErrorObject

  for _, err := range e {
    errs = append(errs, &jsonapi.ErrorObject{
      Title: err.Title,
      Code: err.Code,
      Source: jsonapi.ErrorObjectSource{
        Pointer: err.Pointer,
      },
    })
  }

  return errs
}

type MarshalExperimentalEmbedded

type MarshalExperimentalEmbedded interface {
	UseExperimentalEmbeddedRelationshipData() bool
}

MarshalExperimentalEmbedded interface should be implemented to be able marshal embedded JSON API relationship data.

UseExperimentalEmbeddedRelationshipData example:

type EmbeddedAuthor struct {
  Author
}

func(a EmbeddedAuthor) UseExperimentalEmbeddedRelationshipData() bool {
  return true
}

type MarshalIncluded

type MarshalIncluded interface {
	GetIncluded() []interface{}
}

MarshalIncluded interface should be implemented to be able marshal JSON API document included.

GetIncluded example:

func(v SomeStruct) GetIncluded() []interface{} {
  var included []interface{}

  /*
    Get some additional data here and put it into `items` variables
  `items` data type should implement MarshalResourceIdentifier and MarshalData interface.
  */
  for _, item := range items {
    included = append(included, item)
  }

  return included
}

type MarshalMeta

type MarshalMeta interface {
	GetMeta() interface{}
}

MarshalMeta interface should be implemented to be able marshal JSON API document meta.

GetMeta example:

type Meta struct {
  Count int `json:"count"`
}

func(v SomeStruct) GetMeta() interface{} {
  return Meta{ Count: 42 }
}

type MarshalRelationships

type MarshalRelationships interface {
	GetRelationships() map[string]interface{}
}

MarshalRelationships interface should be implemented to be able marshal JSON API document relationships.

GetRelationships example:

func(s SomeStruct) GetRelationships() map[string]interface{} {
  relationships := make(map[string]interface{})

  relationships["relation"] = jsonapi.ResourceObjectIdentifier{
    ID: s.RelationID,
    Type: "relation-type",
  }

  return relationships
}

type MarshalResourceIdentifier

type MarshalResourceIdentifier interface {
	GetID() string
	GetType() string
}

MarshalResourceIdentifier interface should be implemented to be able marshal Go struct into JSON API document.

GetID example:

func(s SomeStruct) GetID() string {
  return s.ID
}

GetType examples:

func(s SomeStruct) GetType() string {
  return d.Type
}

or

func(s SomeStruct) GetType() string {
  return "some-resource-type"
}

type ResourceObject

type ResourceObject struct {
	ResourceObjectIdentifier
	// Attributes JSON API document attributes raw data.
	Attributes json.RawMessage `json:"attributes,omitempty"`
	// Meta JSON API document meta raw data.
	Meta json.RawMessage `json:"meta,omitempty"`
	// Relationships JSON API document relationships raw data.
	Relationships map[string]*relationship `json:"relationships,omitempty"`
}

ResourceObject extends ResourceObjectIdentifier with JSON API document Attributes, Meta and Relationships.

type ResourceObjectIdentifier

type ResourceObjectIdentifier struct {
	Type string `json:"type"`
	ID   string `json:"id,omitempty"`
}

ResourceObjectIdentifier JSON API resource object.

func (ResourceObjectIdentifier) GetID

func (roi ResourceObjectIdentifier) GetID() string

GetID method returns ResourceObjectIdentifier ID.

func (ResourceObjectIdentifier) GetType

func (roi ResourceObjectIdentifier) GetType() string

GetType method returns ResourceObjectIdentifier Type.

type UnmarshalData

type UnmarshalData interface {
	SetData(func(interface{}) error) error
}

UnmarshalData interface should be implemented to be able unmarshal data from JSON API document into Go struct.

SetData example:

func(s *SomeStruct) SetData(to func(target interface{}) error) error {
  return to(s)
}

NOTE: If you are using SomeStruct collections, you should implement additional data type, e.g.: type SomeStructs []SomeStruct

Then you should implement SetData method for SomeStructs:

func(s *SomeStructs) SetData(to func(target interface{}) error) error {
  return to(s)
}

type UnmarshalErrors

type UnmarshalErrors interface {
	SetErrors(errors []*ErrorObject) error
}

UnmarshalErrors interface should be implemented to be able unmarshal errors from JSON API document.

SetErrors example:

type SomeError struct {
   Code string
   Title string
   Pointer string
 }

type SomeErrors struct {
  Errors []SomeError
}

func(v SomeErrors) SetErrors(errs []*jsonapi.ErrorObject) error {
  var someErrors []SomeError

  for _, err := range errs {
    someErrors = append(someErrors, SomeError{
      Title: err.Title,
      Code: err.Code,
      Pointer: err.Source.Pointer,
    })
  }

  v.Errors = someErrors

  return nil
}

type UnmarshalIncluded added in v1.2.0

type UnmarshalIncluded interface {
	SetIncluded([]*ResourceObject, func(included *ResourceObject, target interface{}) error) error
}

UnmarshalIncluded interface should be implemented to be able unmarshal JSON API document resources.

SetIncluded example:

type Library struct {
	ID      string  `json:"-"`
	Type    string  `json:"-"`
	Books   Books   `json:"-"`
	Authors Authors `json:"-"`
}

func (l *Library) SetIncluded(relationships []*ResourceObject, unmarshal func(included *ResourceObject, target interface{}) error) error {
	for _, relationship := range relationships {
		switch relationship.Type {
		case "authors":
			author := &Author{}
			if err := unmarshal(relationship, author); err != nil {
				return err
			}

			l.Authors = append(l.Authors, *author)
		case "books":
			book := &Book{}
			if err := unmarshal(relationship, book); err != nil {
				return err
			}

			l.Books = append(l.Books, *book)
		}
	}

	return nil
}

type UnmarshalMeta

type UnmarshalMeta interface {
	SetMeta(func(interface{}) error) error
}

UnmarshalMeta interface should be implemented to be able unmarshal JSON API document meta.

SetMeta example:

type BooksViewWithMeta struct {
	BooksView
	Meta BooksMeta `json:"-"`
}

func (v *BooksViewWithMeta) SetMeta(to func(target interface{}) error) error {
	return to(&v.Meta)
}

type BooksMeta struct {
	Count int `json:"count"`
}

type UnmarshalRelationships

type UnmarshalRelationships interface {
	SetRelationships(map[string]interface{}) error
}

UnmarshalRelationships interface should be implemented to be able unmarshal JSON API document relationships into Go struct.

SetRelationships example:

func (s *SomeStruct) SetRelationships(relationships map[string]interface{}) error {
	if relationship, ok := relationships["relation"]; ok {
		s.RealtionID = relationship.(*jsonapi.ResourceObjectIdentifier).ID
	}

	return nil
}

type UnmarshalResourceIdentifier

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

UnmarshalResourceIdentifier interface should be implemented to be able unmarshal JSON API document into Go struct.

SetID, SetType examples:

func(s *SomeStruct) SetID(id string) error {
  s.ID = id
  return nil
}

func(s *SomeStruct) SetType(t string) error {
  s.Type = t
  return nil
}

or

func(s *SomeStruct) SetType(string) error {
  return nil
}

Jump to

Keyboard shortcuts

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