From 824c4edcc952e81d84b4b5e243e491af7faf6d51 Mon Sep 17 00:00:00 2001 From: henrmota Date: Fri, 12 Oct 2018 13:32:15 +0100 Subject: [PATCH 1/4] Add test --- models_test.go | 2 +- response_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/models_test.go b/models_test.go index d443378..21a3a35 100644 --- a/models_test.go +++ b/models_test.go @@ -166,7 +166,7 @@ type Company struct { type Team struct { Name string `jsonapi:"attr,name"` - Leader *Employee `jsonapi:"attr,leader"` + Leader Employee `jsonapi:"attr,leader"` Members []Employee `jsonapi:"attr,members"` } diff --git a/response_test.go b/response_test.go index dc89c48..7783b6f 100644 --- a/response_test.go +++ b/response_test.go @@ -902,6 +902,33 @@ func TestMarshal_InvalidIntefaceArgument(t *testing.T) { } } +func TestMarshalNestedStruct(t *testing.T) { + team := Team{ + Name: "Awesome team", + Leader: Employee{ + Firstname: "John", + Surname: "Mota", + Age: 35, + }, + Members: []Employee{ + { + Firstname: "Henrique", + Surname: "Doe", + }, + }, + } + + buffer := bytes.NewBuffer(nil) + MarshalOnePayloadEmbedded(buffer, &team) + reader := bytes.NewReader(buffer.Bytes()) + var finalTeam Team + UnmarshalPayload(reader, &finalTeam) + + if !reflect.DeepEqual(team, finalTeam) { + t.Error("final unmarshal payload should be equal to the original one.") + } +} + func testBlog() *Blog { return &Blog{ ID: 5, From 7b0afb955beaaf7186a5de85d48cfc93b5809acc Mon Sep 17 00:00:00 2001 From: henrmota Date: Fri, 12 Oct 2018 13:33:23 +0100 Subject: [PATCH 2/4] Add struct handling --- request.go | 47 ++++++++++++++++++++++++++++++++++++++--------- response.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/request.go b/request.go index b9883f2..f752a58 100644 --- a/request.go +++ b/request.go @@ -247,15 +247,27 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) } structField := fieldType - value, err := unmarshalAttribute(attribute, args, structField, fieldValue) - if err != nil { - er = err - break - } - assign(fieldValue, value) - continue + if structField.Type.Kind() != reflect.Struct || + fieldValue.Type() == reflect.TypeOf(new(time.Time)) || + fieldValue.Type() == reflect.TypeOf(time.Time{}) { + value, err := unmarshalAttribute(attribute, args, structField, fieldValue) + if err != nil { + er = err + break + } + assign(fieldValue, value) + continue + } else { + structModel, err := unmarshalFromAttribute(attribute, fieldValue) + if err != nil { + er = err + break + } + fieldValue.Set((*structModel).Elem()) + continue + } } else if annotation == annotationRelation { isSlice := fieldValue.Type().Kind() == reflect.Slice @@ -334,6 +346,23 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) return er } +func unmarshalFromAttribute(attribute interface{}, fieldValue reflect.Value) (*reflect.Value, error) { + structData, err := json.Marshal(attribute) + if err != nil { + return nil, err + } + structNode := new(Node) + if err := json.Unmarshal(structData, &structNode.Attributes); err != nil { + return nil, err + } + structModel := reflect.New(fieldValue.Type()) + if err := unmarshalNode(structNode, structModel, nil); err != nil { + return nil, err + } + + return &structModel, nil +} + func fullNode(n *Node, included *map[string]*Node) *Node { includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) @@ -347,7 +376,7 @@ func fullNode(n *Node, included *map[string]*Node) *Node { // assign will take the value specified and assign it to the field; if // field is expecting a ptr assign will assign a ptr. func assign(field, value reflect.Value) { - if field.Kind() == reflect.Ptr { + if field.Kind() == reflect.Ptr || field.Kind() == reflect.Struct{ field.Set(value) } else { field.Set(reflect.Indirect(value)) @@ -362,6 +391,7 @@ func unmarshalAttribute( value = reflect.ValueOf(attribute) fieldType := structField.Type + // Handle field of type []string if fieldValue.Type() == reflect.TypeOf([]string{}) { value, err = handleStringSlice(attribute) @@ -566,7 +596,6 @@ func handlePointer( func handleStruct( attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { - data, err := json.Marshal(attribute) if err != nil { return reflect.Value{}, err diff --git a/response.go b/response.go index e8e85fa..0c0f368 100644 --- a/response.go +++ b/response.go @@ -207,7 +207,13 @@ func visitModelNode(model interface{}, included *map[string]*Node, node := new(Node) var er error - value := reflect.ValueOf(model) + var value reflect.Value + if modelValue, ok := model.(reflect.Value); ok { + value = modelValue.Addr() + } else { + value = reflect.ValueOf(model) + } + if value.IsNil() { return nil, nil } @@ -340,6 +346,28 @@ func visitModelNode(model interface{}, included *map[string]*Node, node.Attributes[args[1]] = tm.Unix() } } + } else if fieldValue.Kind() == reflect.Slice && fieldValue.Type().Elem().Kind() == reflect.Struct { + newSlice := make([]map[string]interface{}, fieldValue.Len()) + for i:=0; i < fieldValue.Len(); i++ { + included := make(map[string]*Node) + nested, err := visitModelNode(fieldValue.Index(i), &included, true) + if err != nil { + er = err + break + } + + newSlice[i] = nested.Attributes + } + node.Attributes[args[1]] = newSlice + } else if fieldValue.Kind() == reflect.Struct { + included := make(map[string]*Node) + nested, err := visitModelNode(fieldValue, &included, true) + if err != nil { + er = err + break + } + + node.Attributes[args[1]] = nested.Attributes } else { // Dealing with a fieldValue that is not a time emptyValue := reflect.Zero(fieldValue.Type()) From 363160ef5b43b251965761be81738bcb7b4a3f13 Mon Sep 17 00:00:00 2001 From: henrmota Date: Sat, 13 Oct 2018 21:10:20 +0100 Subject: [PATCH 3/4] Add improvements --- models_test.go | 2 +- request.go | 59 ++++++++++++++++++++---------------------------- response.go | 6 ++++- response_test.go | 32 ++++++++++++++++++++++---- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/models_test.go b/models_test.go index 21a3a35..17efc60 100644 --- a/models_test.go +++ b/models_test.go @@ -159,7 +159,7 @@ func (bc *BadComment) JSONAPILinks() *Links { type Company struct { ID string `jsonapi:"primary,companies"` Name string `jsonapi:"attr,name"` - Boss Employee `jsonapi:"attr,boss"` + Boss *Employee `jsonapi:"attr,boss"` Teams []Team `jsonapi:"attr,teams"` FoundedAt time.Time `jsonapi:"attr,founded-at,iso8601"` } diff --git a/request.go b/request.go index f752a58..96d99aa 100644 --- a/request.go +++ b/request.go @@ -248,26 +248,14 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) structField := fieldType - if structField.Type.Kind() != reflect.Struct || - fieldValue.Type() == reflect.TypeOf(new(time.Time)) || - fieldValue.Type() == reflect.TypeOf(time.Time{}) { - value, err := unmarshalAttribute(attribute, args, structField, fieldValue) - if err != nil { - er = err - break - } - assign(fieldValue, value) - continue - - } else { - structModel, err := unmarshalFromAttribute(attribute, fieldValue) - if err != nil { - er = err - break - } - fieldValue.Set((*structModel).Elem()) - continue + value, err := unmarshalAttribute(attribute, args, structField, fieldValue) + if err != nil { + er = err + break } + + assign(fieldValue, value) + continue } else if annotation == annotationRelation { isSlice := fieldValue.Type().Kind() == reflect.Slice @@ -346,21 +334,23 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) return er } -func unmarshalFromAttribute(attribute interface{}, fieldValue reflect.Value) (*reflect.Value, error) { +func unmarshalFromAttribute(attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { structData, err := json.Marshal(attribute) if err != nil { - return nil, err + return reflect.Value{}, err } + structNode := new(Node) if err := json.Unmarshal(structData, &structNode.Attributes); err != nil { - return nil, err + return reflect.Value{}, err } + structModel := reflect.New(fieldValue.Type()) if err := unmarshalNode(structNode, structModel, nil); err != nil { - return nil, err + return reflect.Value{}, err } - return &structModel, nil + return structModel, nil } func fullNode(n *Node, included *map[string]*Node) *Node { @@ -376,7 +366,7 @@ func fullNode(n *Node, included *map[string]*Node) *Node { // assign will take the value specified and assign it to the field; if // field is expecting a ptr assign will assign a ptr. func assign(field, value reflect.Value) { - if field.Kind() == reflect.Ptr || field.Kind() == reflect.Struct{ + if field.Kind() == reflect.Ptr { field.Set(value) } else { field.Set(reflect.Indirect(value)) @@ -391,7 +381,6 @@ func unmarshalAttribute( value = reflect.ValueOf(attribute) fieldType := structField.Type - // Handle field of type []string if fieldValue.Type() == reflect.TypeOf([]string{}) { value, err = handleStringSlice(attribute) @@ -402,12 +391,13 @@ func unmarshalAttribute( if fieldValue.Type() == reflect.TypeOf(time.Time{}) || fieldValue.Type() == reflect.TypeOf(new(time.Time)) { value, err = handleTime(attribute, args, fieldValue) + return } // Handle field of type struct - if fieldValue.Type().Kind() == reflect.Struct { - value, err = handleStruct(attribute, fieldValue) + if fieldValue.Kind() == reflect.Struct { + value, err = unmarshalFromAttribute(attribute, fieldValue) return } @@ -426,7 +416,7 @@ func unmarshalAttribute( // Field was a Pointer type if fieldValue.Kind() == reflect.Ptr { - value, err = handlePointer(attribute, args, fieldType, fieldValue, structField) + value, err = handlePointer(attribute, fieldType, fieldValue, structField) return } @@ -482,7 +472,6 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } var at int64 - if v.Kind() == reflect.Float64 { at = int64(v.Interface().(float64)) } else if v.Kind() == reflect.Int { @@ -492,7 +481,6 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } t := time.Unix(at, 0) - return reflect.ValueOf(t), nil } @@ -558,7 +546,6 @@ func handleNumeric( func handlePointer( attribute interface{}, - args []string, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) (reflect.Value, error) { @@ -574,11 +561,13 @@ func handlePointer( concreteVal = reflect.ValueOf(&cVal) case map[string]interface{}: var err error - concreteVal, err = handleStruct(attribute, fieldValue) + fieldValueType := reflect.New(fieldValue.Type().Elem()).Elem() + concreteVal, err = unmarshalFromAttribute(attribute, fieldValueType) if err != nil { return reflect.Value{}, newErrUnsupportedPtrType( reflect.ValueOf(attribute), fieldType, structField) } + return concreteVal, err default: return reflect.Value{}, newErrUnsupportedPtrType( @@ -624,13 +613,13 @@ func handleStruct( func handleStructSlice( attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { + models := reflect.New(fieldValue.Type()).Elem() dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) for _, data := range dataMap { model := reflect.New(fieldValue.Type().Elem()).Elem() - value, err := handleStruct(data, model) - + value, err := unmarshalFromAttribute(data, model) if err != nil { continue } diff --git a/response.go b/response.go index 0c0f368..28daf87 100644 --- a/response.go +++ b/response.go @@ -359,8 +359,12 @@ func visitModelNode(model interface{}, included *map[string]*Node, newSlice[i] = nested.Attributes } node.Attributes[args[1]] = newSlice - } else if fieldValue.Kind() == reflect.Struct { + } else if fieldValue.Kind() == reflect.Struct || + (fieldValue.Kind() == reflect.Ptr && fieldValue.Elem().Kind() == reflect.Struct) { included := make(map[string]*Node) + if fieldValue.Kind() == reflect.Ptr { + fieldValue = fieldValue.Elem() + } nested, err := visitModelNode(fieldValue, &included, true) if err != nil { er = err diff --git a/response_test.go b/response_test.go index 7783b6f..bf5b7e1 100644 --- a/response_test.go +++ b/response_test.go @@ -918,13 +918,37 @@ func TestMarshalNestedStruct(t *testing.T) { }, } + now := time.Now() + company := Company { + ID: "an_id", + Name: "Awesome Company", + Boss: &Employee{ + Firstname: "Company", + Surname: "boss", + Age: 60, + }, + Teams: []Team { + team, + }, + FoundedAt: now, + } + buffer := bytes.NewBuffer(nil) - MarshalOnePayloadEmbedded(buffer, &team) + MarshalOnePayloadEmbedded(buffer, &company) reader := bytes.NewReader(buffer.Bytes()) - var finalTeam Team - UnmarshalPayload(reader, &finalTeam) + var finalCompany Company + UnmarshalPayload(reader, &finalCompany) + + diff := company.FoundedAt.Sub(finalCompany.FoundedAt) + + if diff.Seconds() > 1 { + t.Error("final unmarshal payload founded at must be approximately equal to the original.") + } + + company.FoundedAt = time.Time{} + finalCompany.FoundedAt = time.Time{} - if !reflect.DeepEqual(team, finalTeam) { + if !reflect.DeepEqual(company, finalCompany) { t.Error("final unmarshal payload should be equal to the original one.") } } From 010053ea39a37f81cdbe7540143bb48e5482c6ec Mon Sep 17 00:00:00 2001 From: henrmota Date: Tue, 16 Oct 2018 15:10:42 +0100 Subject: [PATCH 4/4] Add struct support --- request.go | 39 +++++++++++++++++++++++++++++---------- response.go | 13 +++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/request.go b/request.go index a7bb0b1..f4bdefb 100644 --- a/request.go +++ b/request.go @@ -247,6 +247,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) } structField := fieldType + value, err := unmarshalAttribute(attribute, args, structField, fieldValue) if err != nil { er = err @@ -332,6 +333,25 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) return er } +func unmarshalFromAttribute(attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { + structData, err := json.Marshal(attribute) + if err != nil { + return reflect.Value{}, err + } + + structNode := new(Node) + if err := json.Unmarshal(structData, &structNode.Attributes); err != nil { + return reflect.Value{}, err + } + + structModel := reflect.New(fieldValue.Type()) + if err := unmarshalNode(structNode, structModel, nil); err != nil { + return reflect.Value{}, err + } + + return structModel, nil +} + func fullNode(n *Node, included *map[string]*Node) *Node { includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) @@ -397,12 +417,13 @@ func unmarshalAttribute( if fieldValue.Type() == reflect.TypeOf(time.Time{}) || fieldValue.Type() == reflect.TypeOf(new(time.Time)) { value, err = handleTime(attribute, args, fieldValue) + return } // Handle field of type struct - if fieldValue.Type().Kind() == reflect.Struct { - value, err = handleStruct(attribute, fieldValue) + if fieldValue.Kind() == reflect.Struct { + value, err = unmarshalFromAttribute(attribute, fieldValue) return } @@ -421,7 +442,7 @@ func unmarshalAttribute( // Field was a Pointer type if fieldValue.Kind() == reflect.Ptr { - value, err = handlePointer(attribute, args, fieldType, fieldValue, structField) + value, err = handlePointer(attribute, fieldType, fieldValue, structField) return } @@ -477,7 +498,6 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } var at int64 - if v.Kind() == reflect.Float64 { at = int64(v.Interface().(float64)) } else if v.Kind() == reflect.Int { @@ -487,7 +507,6 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } t := time.Unix(at, 0) - return reflect.ValueOf(t), nil } @@ -553,7 +572,6 @@ func handleNumeric( func handlePointer( attribute interface{}, - args []string, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) (reflect.Value, error) { @@ -569,11 +587,13 @@ func handlePointer( concreteVal = reflect.ValueOf(&cVal) case map[string]interface{}: var err error - concreteVal, err = handleStruct(attribute, fieldValue) + fieldValueType := reflect.New(fieldValue.Type().Elem()).Elem() + concreteVal, err = unmarshalFromAttribute(attribute, fieldValueType) if err != nil { return reflect.Value{}, newErrUnsupportedPtrType( reflect.ValueOf(attribute), fieldType, structField) } + return concreteVal, err default: return reflect.Value{}, newErrUnsupportedPtrType( @@ -591,7 +611,6 @@ func handlePointer( func handleStruct( attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { - data, err := json.Marshal(attribute) if err != nil { return reflect.Value{}, err @@ -619,13 +638,13 @@ func handleStruct( func handleStructSlice( attribute interface{}, fieldValue reflect.Value) (reflect.Value, error) { + models := reflect.New(fieldValue.Type()).Elem() dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) for _, data := range dataMap { model := reflect.New(fieldValue.Type().Elem()).Elem() - value, err := handleStruct(data, model) - + value, err := unmarshalFromAttribute(data, model) if err != nil { continue } diff --git a/response.go b/response.go index 28daf87..b4d2ea8 100644 --- a/response.go +++ b/response.go @@ -347,6 +347,10 @@ func visitModelNode(model interface{}, included *map[string]*Node, } } } else if fieldValue.Kind() == reflect.Slice && fieldValue.Type().Elem().Kind() == reflect.Struct { + if omitEmpty && fieldValue.Len() == 0 { + continue + } + newSlice := make([]map[string]interface{}, fieldValue.Len()) for i:=0; i < fieldValue.Len(); i++ { included := make(map[string]*Node) @@ -361,6 +365,15 @@ func visitModelNode(model interface{}, included *map[string]*Node, node.Attributes[args[1]] = newSlice } else if fieldValue.Kind() == reflect.Struct || (fieldValue.Kind() == reflect.Ptr && fieldValue.Elem().Kind() == reflect.Struct) { + + // Dealing with a fieldValue that is not a time + emptyValue := reflect.Zero(fieldValue.Type()) + + // See if we need to omit this field + if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) { + continue + } + included := make(map[string]*Node) if fieldValue.Kind() == reflect.Ptr { fieldValue = fieldValue.Elem()