151 lines
4.5 KiB
Go
151 lines
4.5 KiB
Go
|
package jsonapi
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
)
|
||
|
|
||
|
var objectSuffix = []byte("{")
|
||
|
var arraySuffix = []byte("[")
|
||
|
var stringSuffix = []byte(`"`)
|
||
|
|
||
|
// A Document represents a JSON API document as specified here: http://jsonapi.org.
|
||
|
type Document struct {
|
||
|
Links Links `json:"links,omitempty"`
|
||
|
Data *DataContainer `json:"data"`
|
||
|
Included []Data `json:"included,omitempty"`
|
||
|
Meta map[string]interface{} `json:"meta,omitempty"`
|
||
|
}
|
||
|
|
||
|
// A DataContainer is used to marshal and unmarshal single objects and arrays
|
||
|
// of objects.
|
||
|
type DataContainer struct {
|
||
|
DataObject *Data
|
||
|
DataArray []Data
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON unmarshals the JSON-encoded data to the DataObject field if the
|
||
|
// root element is an object or to the DataArray field for arrays.
|
||
|
func (c *DataContainer) UnmarshalJSON(payload []byte) error {
|
||
|
if bytes.HasPrefix(payload, objectSuffix) {
|
||
|
return json.Unmarshal(payload, &c.DataObject)
|
||
|
}
|
||
|
|
||
|
if bytes.HasPrefix(payload, arraySuffix) {
|
||
|
return json.Unmarshal(payload, &c.DataArray)
|
||
|
}
|
||
|
|
||
|
return errors.New("expected a JSON encoded object or array")
|
||
|
}
|
||
|
|
||
|
// MarshalJSON returns the JSON encoding of the DataArray field or the DataObject
|
||
|
// field. It will return "null" if neither of them is set.
|
||
|
func (c *DataContainer) MarshalJSON() ([]byte, error) {
|
||
|
if c.DataArray != nil {
|
||
|
return json.Marshal(c.DataArray)
|
||
|
}
|
||
|
|
||
|
return json.Marshal(c.DataObject)
|
||
|
}
|
||
|
|
||
|
// Link represents a link for return in the document.
|
||
|
type Link struct {
|
||
|
Href string `json:"href"`
|
||
|
Meta map[string]interface{} `json:"meta,omitempty"`
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON marshals a string value into the Href field or marshals an
|
||
|
// object value into the whole struct.
|
||
|
func (l *Link) UnmarshalJSON(payload []byte) error {
|
||
|
if bytes.HasPrefix(payload, stringSuffix) {
|
||
|
return json.Unmarshal(payload, &l.Href)
|
||
|
}
|
||
|
|
||
|
if bytes.HasPrefix(payload, objectSuffix) {
|
||
|
obj := make(map[string]interface{})
|
||
|
err := json.Unmarshal(payload, &obj)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
var ok bool
|
||
|
l.Href, ok = obj["href"].(string)
|
||
|
if !ok {
|
||
|
return errors.New(`link object expects a "href" key`)
|
||
|
}
|
||
|
l.Meta, _ = obj["meta"].(map[string]interface{})
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return errors.New("expected a JSON encoded string or object")
|
||
|
}
|
||
|
|
||
|
// MarshalJSON returns the JSON encoding of only the Href field if the Meta
|
||
|
// field is empty, otherwise it marshals the whole struct.
|
||
|
func (l Link) MarshalJSON() ([]byte, error) {
|
||
|
if len(l.Meta) == 0 {
|
||
|
return json.Marshal(l.Href)
|
||
|
}
|
||
|
return json.Marshal(map[string]interface{}{
|
||
|
"href": l.Href,
|
||
|
"meta": l.Meta,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Links contains a map of custom Link objects as given by an element.
|
||
|
type Links map[string]Link
|
||
|
|
||
|
// Data is a general struct for document data and included data.
|
||
|
type Data struct {
|
||
|
Type string `json:"type"`
|
||
|
ID string `json:"id"`
|
||
|
Attributes json.RawMessage `json:"attributes"`
|
||
|
Relationships map[string]Relationship `json:"relationships,omitempty"`
|
||
|
Links Links `json:"links,omitempty"`
|
||
|
}
|
||
|
|
||
|
// Relationship contains reference IDs to the related structs
|
||
|
type Relationship struct {
|
||
|
Links Links `json:"links,omitempty"`
|
||
|
Data *RelationshipDataContainer `json:"data,omitempty"`
|
||
|
Meta map[string]interface{} `json:"meta,omitempty"`
|
||
|
}
|
||
|
|
||
|
// A RelationshipDataContainer is used to marshal and unmarshal single relationship
|
||
|
// objects and arrays of relationship objects.
|
||
|
type RelationshipDataContainer struct {
|
||
|
DataObject *RelationshipData
|
||
|
DataArray []RelationshipData
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON unmarshals the JSON-encoded data to the DataObject field if the
|
||
|
// root element is an object or to the DataArray field for arrays.
|
||
|
func (c *RelationshipDataContainer) UnmarshalJSON(payload []byte) error {
|
||
|
if bytes.HasPrefix(payload, objectSuffix) {
|
||
|
// payload is an object
|
||
|
return json.Unmarshal(payload, &c.DataObject)
|
||
|
}
|
||
|
|
||
|
if bytes.HasPrefix(payload, arraySuffix) {
|
||
|
// payload is an array
|
||
|
return json.Unmarshal(payload, &c.DataArray)
|
||
|
}
|
||
|
|
||
|
return errors.New("Invalid json for relationship data array/object")
|
||
|
}
|
||
|
|
||
|
// MarshalJSON returns the JSON encoding of the DataArray field or the DataObject
|
||
|
// field. It will return "null" if neither of them is set.
|
||
|
func (c *RelationshipDataContainer) MarshalJSON() ([]byte, error) {
|
||
|
if c.DataArray != nil {
|
||
|
return json.Marshal(c.DataArray)
|
||
|
}
|
||
|
return json.Marshal(c.DataObject)
|
||
|
}
|
||
|
|
||
|
// RelationshipData represents one specific reference ID.
|
||
|
type RelationshipData struct {
|
||
|
Type string `json:"type"`
|
||
|
ID string `json:"id"`
|
||
|
}
|