forked from Mirrors/jsonapi
support marshalling a list of models
This commit is contained in:
parent
0525400acb
commit
81bf23f93f
16
node.go
16
node.go
|
@ -1,11 +1,21 @@
|
||||||
package jsonapi
|
package jsonapi
|
||||||
|
|
||||||
type JsonApiPayload struct {
|
type JsonApiOnePayload struct {
|
||||||
Data *JsonApiNode `json:"data"`
|
Data *JsonApiNode `json:"data"`
|
||||||
Included []*JsonApiNode `json:"included,omitempty"`
|
Included []*JsonApiNode `json:"included,omitempty"`
|
||||||
Links *map[string]string `json:"links,omitempty"`
|
Links *map[string]string `json:"links,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JsonApiManyPayload struct {
|
||||||
|
Data []*JsonApiNode `json:"data"`
|
||||||
|
Included []*JsonApiNode `json:"included,omitempty"`
|
||||||
|
Links *map[string]string `json:"links,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Models interface {
|
||||||
|
GetData() []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
type JsonApiNode struct {
|
type JsonApiNode struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
@ -13,12 +23,12 @@ type JsonApiNode struct {
|
||||||
Relationships map[string]interface{} `json:"realtionships,omitempty"`
|
Relationships map[string]interface{} `json:"realtionships,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JsonApiRelationshipSingleNode struct {
|
type JsonApiRelationshipOneNode struct {
|
||||||
Data *JsonApiNode `json:"data"`
|
Data *JsonApiNode `json:"data"`
|
||||||
Links *map[string]string `json:"links,omitempty"`
|
Links *map[string]string `json:"links,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JsonApiRelationshipMultipleNode struct {
|
type JsonApiRelationshipManyNode struct {
|
||||||
Data []*JsonApiNode `json:"data"`
|
Data []*JsonApiNode `json:"data"`
|
||||||
Links *map[string]string `json:"links,omitempty"`
|
Links *map[string]string `json:"links,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UnmarshalJsonApiPayload(payload *JsonApiPayload, model interface{}) error {
|
func UnmarshalJsonApiPayload(payload *JsonApiOnePayload, model interface{}) error {
|
||||||
data := payload.Data
|
data := payload.Data
|
||||||
|
|
||||||
modelType := reflect.TypeOf(model).Elem()
|
modelType := reflect.TypeOf(model).Elem()
|
||||||
|
|
|
@ -32,8 +32,8 @@ func TestUnmarshalSetsAttrs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func samplePayload() *JsonApiPayload {
|
func samplePayload() *JsonApiOnePayload {
|
||||||
return &JsonApiPayload{
|
return &JsonApiOnePayload{
|
||||||
Data: &JsonApiNode{
|
Data: &JsonApiNode{
|
||||||
Id: "5",
|
Id: "5",
|
||||||
Type: "blogs",
|
Type: "blogs",
|
||||||
|
|
58
response.go
58
response.go
|
@ -8,13 +8,45 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarshalJsonApiPayload(model interface{}) (*JsonApiPayload, error) {
|
func MarshalJsonApiManyPayload(models Models) (*JsonApiManyPayload, error) {
|
||||||
|
d := models.GetData()
|
||||||
|
data := make([]*JsonApiNode, 0, len(d))
|
||||||
|
|
||||||
|
incl := make([]*JsonApiNode, 0)
|
||||||
|
|
||||||
|
for _, model := range d {
|
||||||
|
node, included, err := visitModelNode(model)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = append(data, node)
|
||||||
|
incl = append(incl, included...)
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueIncluded := make(map[string]*JsonApiNode)
|
||||||
|
|
||||||
|
for i, n := range incl {
|
||||||
|
k := fmt.Sprintf("%s,%s", n.Type, n.Id)
|
||||||
|
if uniqueIncluded[k] == nil {
|
||||||
|
uniqueIncluded[k] = n
|
||||||
|
} else {
|
||||||
|
incl = deleteNode(incl, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JsonApiManyPayload{
|
||||||
|
Data: data,
|
||||||
|
Included: incl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalJsonApiOnePayload(model interface{}) (*JsonApiOnePayload, error) {
|
||||||
rootNode, included, err := visitModelNode(model)
|
rootNode, included, err := visitModelNode(model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &JsonApiPayload{Data: rootNode}
|
resp := &JsonApiOnePayload{Data: rootNode}
|
||||||
|
|
||||||
uniqueIncluded := make(map[string]*JsonApiNode)
|
uniqueIncluded := make(map[string]*JsonApiNode)
|
||||||
|
|
||||||
|
@ -23,7 +55,7 @@ func MarshalJsonApiPayload(model interface{}) (*JsonApiPayload, error) {
|
||||||
if uniqueIncluded[k] == nil {
|
if uniqueIncluded[k] == nil {
|
||||||
uniqueIncluded[k] = n
|
uniqueIncluded[k] = n
|
||||||
} else {
|
} else {
|
||||||
included = append(included[:i], included[i+1:]...)
|
included = deleteNode(included, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +143,7 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Relationships[args[1]] = &JsonApiRelationshipMultipleNode{Data: shallowNodes}
|
node.Relationships[args[1]] = &JsonApiRelationshipManyNode{Data: shallowNodes}
|
||||||
} else {
|
} else {
|
||||||
er = err
|
er = err
|
||||||
return false
|
return false
|
||||||
|
@ -124,7 +156,7 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
|
||||||
|
|
||||||
included = append(included, relationship)
|
included = append(included, relationship)
|
||||||
|
|
||||||
node.Relationships[args[1]] = &JsonApiRelationshipSingleNode{Data: &shallowNode}
|
node.Relationships[args[1]] = &JsonApiRelationshipOneNode{Data: &shallowNode}
|
||||||
} else {
|
} else {
|
||||||
er = err
|
er = err
|
||||||
return false
|
return false
|
||||||
|
@ -147,8 +179,8 @@ func visitModelNode(model interface{}) (*JsonApiNode, []*JsonApiNode, error) {
|
||||||
return node, included, nil
|
return node, included, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipMultipleNode, error) {
|
func visitModelNodeRelationships(relationName string, models reflect.Value) (map[string]*JsonApiRelationshipManyNode, error) {
|
||||||
m := make(map[string]*JsonApiRelationshipMultipleNode)
|
m := make(map[string]*JsonApiRelationshipManyNode)
|
||||||
nodes := make([]*JsonApiNode, 0)
|
nodes := make([]*JsonApiNode, 0)
|
||||||
|
|
||||||
for i := 0; i < models.Len(); i++ {
|
for i := 0; i < models.Len(); i++ {
|
||||||
|
@ -160,7 +192,17 @@ func visitModelNodeRelationships(relationName string, models reflect.Value) (map
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
m[relationName] = &JsonApiRelationshipMultipleNode{Data: nodes}
|
m[relationName] = &JsonApiRelationshipManyNode{Data: nodes}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteNode(a []*JsonApiNode, i int) []*JsonApiNode {
|
||||||
|
if i < len(a)-1 {
|
||||||
|
a = append(a[:i], a[i+1:]...)
|
||||||
|
} else {
|
||||||
|
a = a[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,16 @@ type Blog struct {
|
||||||
ViewCount int `jsonapi:"attr,view_count"`
|
ViewCount int `jsonapi:"attr,view_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Blogs []*Blog
|
||||||
|
|
||||||
|
func (b Blogs) GetData() []interface{} {
|
||||||
|
d := make([]interface{}, len(b))
|
||||||
|
for i, blog := range b {
|
||||||
|
d[i] = blog
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasPrimaryAnnotation(t *testing.T) {
|
func TestHasPrimaryAnnotation(t *testing.T) {
|
||||||
testModel := &Blog{
|
testModel := &Blog{
|
||||||
Id: 5,
|
Id: 5,
|
||||||
|
@ -31,7 +41,7 @@ func TestHasPrimaryAnnotation(t *testing.T) {
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := MarshalJsonApiPayload(testModel)
|
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +64,7 @@ func TestSupportsAttributes(t *testing.T) {
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := MarshalJsonApiPayload(testModel)
|
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +104,7 @@ func TestRelations(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := MarshalJsonApiPayload(testModel)
|
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -126,7 +136,7 @@ func TestRelations(t *testing.T) {
|
||||||
func TestNoRelations(t *testing.T) {
|
func TestNoRelations(t *testing.T) {
|
||||||
testModel := &Blog{Id: 1, Title: "Title 1", CreatedAt: time.Now()}
|
testModel := &Blog{Id: 1, Title: "Title 1", CreatedAt: time.Now()}
|
||||||
|
|
||||||
resp, err := MarshalJsonApiPayload(testModel)
|
resp, err := MarshalJsonApiOnePayload(testModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -137,7 +147,7 @@ func TestNoRelations(t *testing.T) {
|
||||||
|
|
||||||
fmt.Printf("%s\n", jsonBuffer.Bytes())
|
fmt.Printf("%s\n", jsonBuffer.Bytes())
|
||||||
|
|
||||||
decodedResponse := new(JsonApiPayload)
|
decodedResponse := new(JsonApiOnePayload)
|
||||||
|
|
||||||
json.NewDecoder(jsonBuffer).Decode(decodedResponse)
|
json.NewDecoder(jsonBuffer).Decode(decodedResponse)
|
||||||
|
|
||||||
|
@ -145,3 +155,68 @@ func TestNoRelations(t *testing.T) {
|
||||||
t.Fatalf("Encoding json response did not omit included")
|
t.Fatalf("Encoding json response did not omit included")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalMany(t *testing.T) {
|
||||||
|
data := Blogs{
|
||||||
|
&Blog{
|
||||||
|
Id: 5,
|
||||||
|
Title: "Title 1",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Posts: []*Post{
|
||||||
|
&Post{
|
||||||
|
Id: 1,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
},
|
||||||
|
&Post{
|
||||||
|
Id: 2,
|
||||||
|
Title: "Fuubar",
|
||||||
|
Body: "Bas",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CurrentPost: &Post{
|
||||||
|
Id: 1,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&Blog{
|
||||||
|
Id: 6,
|
||||||
|
Title: "Title 2",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Posts: []*Post{
|
||||||
|
&Post{
|
||||||
|
Id: 3,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
},
|
||||||
|
&Post{
|
||||||
|
Id: 4,
|
||||||
|
Title: "Fuubar",
|
||||||
|
Body: "Bas",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CurrentPost: &Post{
|
||||||
|
Id: 4,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := MarshalJsonApiManyPayload(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
json.NewEncoder(out).Encode(resp)
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", out.Bytes())
|
||||||
|
|
||||||
|
d := resp.Data
|
||||||
|
|
||||||
|
if len(d) != 2 {
|
||||||
|
t.Fatalf("data should have two elements")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue