support unmarshal sideloaded records for two-way function

This commit is contained in:
Sam Woodard 2015-07-10 11:41:54 -07:00
parent 0becfab81d
commit b112561a72
3 changed files with 162 additions and 24 deletions

View File

@ -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{})
} }

View File

@ -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
}

View File

@ -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) {