forked from Mirrors/jsonapi
support unmarshal sideloaded records for two-way function
This commit is contained in:
parent
0becfab81d
commit
b112561a72
60
request.go
60
request.go
|
@ -18,10 +18,21 @@ func UnmarshalPayload(in io.Reader, model interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmarshalNode(payload.Data, reflect.ValueOf(model))
|
if payload.Included != nil {
|
||||||
|
includedMap := make(map[string]*Node)
|
||||||
|
for _, included := range payload.Included {
|
||||||
|
key := fmt.Sprintf("%s,%s", included.Type, included.Id)
|
||||||
|
includedMap[key] = included
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap)
|
||||||
|
} else {
|
||||||
|
return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalNode(data *Node, model reflect.Value) error {
|
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) error {
|
||||||
modelValue := model.Elem()
|
modelValue := model.Elem()
|
||||||
modelType := model.Type().Elem()
|
modelType := model.Type().Elem()
|
||||||
|
|
||||||
|
@ -128,9 +139,8 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
||||||
|
|
||||||
for _, r := range data {
|
for _, r := range data {
|
||||||
m := reflect.New(fieldValue.Type().Elem().Elem())
|
m := reflect.New(fieldValue.Type().Elem().Elem())
|
||||||
h := r.(map[string]interface{})
|
|
||||||
|
|
||||||
if err := unmarshalNode(mapToNode(h), m); err != nil {
|
if err := unmarshalMap(r, m, included); err != nil {
|
||||||
er = err
|
er = err
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -141,9 +151,7 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
||||||
fieldValue.Set(models)
|
fieldValue.Set(models)
|
||||||
} else {
|
} else {
|
||||||
m := reflect.New(fieldValue.Type().Elem())
|
m := reflect.New(fieldValue.Type().Elem())
|
||||||
h := relationship["data"].(map[string]interface{})
|
if err := unmarshalMap(relationship["data"], m, included); err != nil {
|
||||||
|
|
||||||
if err := unmarshalNode(mapToNode(h), m); err != nil {
|
|
||||||
er = err
|
er = err
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -166,18 +174,50 @@ func unmarshalNode(data *Node, model reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToNode(m map[string]interface{}) *Node {
|
func unmarshalMap(nodeData interface{}, model reflect.Value, included *map[string]*Node) error {
|
||||||
|
h := nodeData.(map[string]interface{})
|
||||||
|
node := fetchNode(h, included)
|
||||||
|
|
||||||
|
if err := unmarshalNode(node, model, included); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchNode(m map[string]interface{}, included *map[string]*Node) *Node {
|
||||||
|
var attributes map[string]interface{}
|
||||||
|
var relationships map[string]interface{}
|
||||||
|
|
||||||
|
if included != nil {
|
||||||
|
key := fmt.Sprintf("%s,%s", m["type"].(string), m["id"].(string))
|
||||||
|
node := (*included)[key]
|
||||||
|
|
||||||
|
if node != nil {
|
||||||
|
attributes = node.Attributes
|
||||||
|
relationships = node.Relationships
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapToNode(m, attributes, relationships)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToNode(m map[string]interface{}, attributes map[string]interface{}, relationships map[string]interface{}) *Node {
|
||||||
node := &Node{Type: m["type"].(string)}
|
node := &Node{Type: m["type"].(string)}
|
||||||
|
|
||||||
if m["id"] != nil {
|
if m["id"] != nil {
|
||||||
node.Id = m["id"].(string)
|
node.Id = m["id"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m["attributes"] != nil {
|
if m["attributes"] == nil {
|
||||||
|
node.Attributes = attributes
|
||||||
|
} else {
|
||||||
node.Attributes = m["attributes"].(map[string]interface{})
|
node.Attributes = m["attributes"].(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if m["relationships"] != nil {
|
if m["relationships"] == nil {
|
||||||
|
node.Relationships = relationships
|
||||||
|
} else {
|
||||||
node.Relationships = m["relationships"].(map[string]interface{})
|
node.Relationships = m["relationships"].(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
110
request_test.go
110
request_test.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BadModel struct {
|
type BadModel struct {
|
||||||
|
@ -45,11 +46,6 @@ func TestUnmarshalSetsAttrs(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//o := bytes.NewBuffer(nil)
|
|
||||||
//json.NewEncoder(o).Encode(out)
|
|
||||||
|
|
||||||
//fmt.Printf("%s\n", o.Bytes())
|
|
||||||
|
|
||||||
if out.CreatedAt.IsZero() {
|
if out.CreatedAt.IsZero() {
|
||||||
t.Fatalf("Did not parse time")
|
t.Fatalf("Did not parse time")
|
||||||
}
|
}
|
||||||
|
@ -70,7 +66,7 @@ func TestUnmarshalRelationships(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if out.CurrentPost.Title != "Bas" || out.CurrentPost.Body != "Fuubar" {
|
if out.CurrentPost.Title != "Bas" || out.CurrentPost.Body != "Fuubar" {
|
||||||
t.Fatalf("Attributes where not set")
|
t.Fatalf("Attributes were not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(out.Posts) != 2 {
|
if len(out.Posts) != 2 {
|
||||||
|
@ -97,6 +93,48 @@ func TestUnmarshalNestedRelationships(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRelationshipsSideloaded(t *testing.T) {
|
||||||
|
payload := samplePayloadWithSideloaded()
|
||||||
|
out := new(Blog)
|
||||||
|
|
||||||
|
if err := UnmarshalPayload(payload, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.CurrentPost == nil {
|
||||||
|
t.Fatalf("Current post was not materialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.CurrentPost.Title != "Foo" || out.CurrentPost.Body != "Bar" {
|
||||||
|
t.Fatalf("Attributes were not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out.Posts) != 2 {
|
||||||
|
t.Fatalf("There should have been 2 posts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalNestedRelationshipsSideloaded(t *testing.T) {
|
||||||
|
payload := samplePayloadWithSideloaded()
|
||||||
|
out := new(Blog)
|
||||||
|
|
||||||
|
if err := UnmarshalPayload(payload, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.CurrentPost == nil {
|
||||||
|
t.Fatalf("Current post was not materialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.CurrentPost.Comments == nil {
|
||||||
|
t.Fatalf("Did not materialize nested records, comments")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out.CurrentPost.Comments) != 2 {
|
||||||
|
t.Fatalf("Wrong number of comments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func unmarshalSamplePayload() (*Blog, error) {
|
func unmarshalSamplePayload() (*Blog, error) {
|
||||||
in := samplePayload()
|
in := samplePayload()
|
||||||
out := new(Blog)
|
out := new(Blog)
|
||||||
|
@ -192,3 +230,63 @@ func samplePayloadWithId() io.Reader {
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func samplePayloadWithSideloaded() io.Reader {
|
||||||
|
testModel := &Blog{
|
||||||
|
Id: 5,
|
||||||
|
Title: "Title 1",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Posts: []*Post{
|
||||||
|
&Post{
|
||||||
|
Id: 1,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
Comments: []*Comment{
|
||||||
|
&Comment{
|
||||||
|
Id: 1,
|
||||||
|
Body: "foo",
|
||||||
|
},
|
||||||
|
&Comment{
|
||||||
|
Id: 2,
|
||||||
|
Body: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&Post{
|
||||||
|
Id: 2,
|
||||||
|
Title: "Fuubar",
|
||||||
|
Body: "Bas",
|
||||||
|
Comments: []*Comment{
|
||||||
|
&Comment{
|
||||||
|
Id: 1,
|
||||||
|
Body: "foo",
|
||||||
|
},
|
||||||
|
&Comment{
|
||||||
|
Id: 3,
|
||||||
|
Body: "bas",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CurrentPost: &Post{
|
||||||
|
Id: 1,
|
||||||
|
Title: "Foo",
|
||||||
|
Body: "Bar",
|
||||||
|
Comments: []*Comment{
|
||||||
|
&Comment{
|
||||||
|
Id: 1,
|
||||||
|
Body: "foo",
|
||||||
|
},
|
||||||
|
&Comment{
|
||||||
|
Id: 2,
|
||||||
|
Body: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
MarshalOnePayload(out, testModel)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
16
response.go
16
response.go
|
@ -163,10 +163,10 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if sideload {
|
if sideload {
|
||||||
|
included = append(included, d...)
|
||||||
shallowNodes := make([]*Node, 0)
|
shallowNodes := make([]*Node, 0)
|
||||||
for _, node := range d {
|
for _, node := range d {
|
||||||
included = append(included, node)
|
shallowNodes = append(shallowNodes, toShallowNode(node))
|
||||||
shallowNodes = append(shallowNodes, cloneAndRemoveAttributes(node))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Relationships[args[1]] = &RelationshipManyNode{Data: shallowNodes}
|
node.Relationships[args[1]] = &RelationshipManyNode{Data: shallowNodes}
|
||||||
|
@ -182,7 +182,7 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if sideload {
|
if sideload {
|
||||||
included = append(included, relationship)
|
included = append(included, relationship)
|
||||||
node.Relationships[args[1]] = &RelationshipOneNode{Data: cloneAndRemoveAttributes(relationship)}
|
node.Relationships[args[1]] = &RelationshipOneNode{Data: toShallowNode(relationship)}
|
||||||
} else {
|
} else {
|
||||||
node.Relationships[args[1]] = &RelationshipOneNode{Data: relationship}
|
node.Relationships[args[1]] = &RelationshipOneNode{Data: relationship}
|
||||||
}
|
}
|
||||||
|
@ -208,11 +208,11 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) {
|
||||||
return node, included, nil
|
return node, included, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneAndRemoveAttributes(node *Node) *Node {
|
func toShallowNode(node *Node) *Node {
|
||||||
n := *node
|
return &Node{
|
||||||
n.Attributes = nil
|
Id: node.Id,
|
||||||
|
Type: node.Type,
|
||||||
return &n
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*RelationshipManyNode, error) {
|
func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*RelationshipManyNode, error) {
|
||||||
|
|
Loading…
Reference in New Issue