diff --git a/README.md b/README.md index 23d9edb..1aeb279 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Decorators that can be used - `index` -- marks field to have an index applied to it. - `unique` -- marks field to have unique constraint. - `pk` -- marks field as a primary key. Can only have one pk, composite pk's are not supported. -- `properties` -- marks that field is using a map. GoGM only supports properties fields of `map[string]interface{}` +- `properties` -- marks that field is using a map. GoGM only supports properties fields of `map[string]interface{}`, `map[string]`, `map[string][]` and `[]` - `-` -- marks that field will be ignored by the ogm #### Not on relationship member variables @@ -76,14 +76,16 @@ type VertexA struct { // provides required node fields gogm.BaseNode - TestField string `gogm:"name=test_field"` - TestTypeDefString tdString `gogm:"name=test_type_def_string"` - TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` - SingleA *VertexB `gogm:"direction=incoming;relationship=test_rel"` - ManyA []*VertexB `gogm:"direction=incoming;relationship=testm2o"` - MultiA []*VertexB `gogm:"direction=incoming;relationship=multib"` - SingleSpecA *EdgeC `gogm:"direction=outgoing;relationship=special_single"` - MultiSpecA []*EdgeC `gogm:"direction=outgoing;relationship=special_multi"` +TestField string `gogm:"name=test_field"` + TestTypeDefString tdString `gogm:"name=test_type_def_string"` + TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` + MapProperty map[string]string `gogm:"name=map_property;properties"` + SliceProperty []string `gogm:"name=slice_property;properties"` + SingleA *VertexB `gogm:"direction=incoming;relationship=test_rel"` + ManyA []*VertexB `gogm:"direction=incoming;relationship=testm2o"` + MultiA []*VertexB `gogm:"direction=incoming;relationship=multib"` + SingleSpecA *EdgeC `gogm:"direction=outgoing;relationship=special_single"` + MultiSpecA []*EdgeC `gogm:"direction=outgoing;relationship=special_multi"` } type VertexB struct { diff --git a/decoder.go b/decoder.go index 47cc51b..e705de6 100644 --- a/decoder.go +++ b/decoder.go @@ -553,25 +553,6 @@ func convertToValue(graphId int64, conf structDecoratorConfig, props map[string] continue } - if fieldConfig.Properties { - mapType := reflect.MapOf(reflect.TypeOf(""), emptyInterfaceType) - mapVal := reflect.MakeMap(mapType) - - for k, v := range props { - if !strings.Contains(k, fieldConfig.Name) { - //not one of our map fields - continue - } - - mapKey := strings.Replace(k, fieldConfig.Name+".", "", 1) - - mapVal.SetMapIndex(reflect.ValueOf(mapKey), reflect.ValueOf(v)) - } - - reflect.Indirect(val).FieldByName(field).Set(mapVal) - continue - } - var raw interface{} var ok bool @@ -585,10 +566,81 @@ func convertToValue(graphId int64, conf structDecoratorConfig, props map[string] rawVal := reflect.ValueOf(raw) - if raw == nil || rawVal.IsZero() { - continue //its already initialized to 0 value, no need to do anything + indirect := reflect.Indirect(val) + if fieldConfig.Properties && fieldConfig.PropConfig != nil { + if fieldConfig.PropConfig.IsMap { + var sub reflect.Type + if fieldConfig.PropConfig.IsMapSlice { + sub = reflect.SliceOf(fieldConfig.PropConfig.SubType) + } else { + sub = fieldConfig.PropConfig.SubType + } + mapType := reflect.MapOf(reflect.TypeOf(""), sub) + mapVal := reflect.MakeMap(mapType) + for k, v := range props { + if !strings.Contains(k, fieldConfig.Name) { + //not one of our map fields + continue + } + + mapKey := strings.Replace(k, fieldConfig.Name+".", "", 1) + + if fieldConfig.PropConfig.IsMapSlice { + if v == nil { + // skip if nil + continue + } + + sliceVal := reflect.ValueOf(v) + + if sliceVal.IsZero() { + // cant do anything with a zero value + continue + } + rawLen := sliceVal.Len() + sl := reflect.MakeSlice(reflect.SliceOf(fieldConfig.PropConfig.SubType), rawLen, sliceVal.Cap()) + + for i := 0; i < rawLen; i++ { + slVal := sliceVal.Index(i) + if fieldConfig.PropConfig.SubType == slVal.Type() { + sl.Index(i).Set(slVal) + } else { + sl.Index(i).Set(slVal.Elem().Convert(fieldConfig.PropConfig.SubType)) + } + } + mapVal.SetMapIndex(reflect.ValueOf(mapKey), sl) + } else { + vVal := reflect.ValueOf(v) + if fieldConfig.PropConfig.SubType == vVal.Type() { + mapVal.SetMapIndex(reflect.ValueOf(mapKey), vVal) + } else { + mapVal.SetMapIndex(reflect.ValueOf(mapKey), vVal.Convert(fieldConfig.PropConfig.SubType)) + } + } + } + indirect.FieldByName(field).Set(mapVal) + } else { + if raw == nil || rawVal.IsZero() { + // cant do anything with a zero value + continue + } + rawLen := rawVal.Len() + sl := reflect.MakeSlice(reflect.SliceOf(fieldConfig.PropConfig.SubType), rawLen, rawVal.Cap()) + + for i := 0; i < rawLen; i++ { + slVal := rawVal.Index(i) + if fieldConfig.PropConfig.SubType == slVal.Type() { + sl.Index(i).Set(slVal) + } else { + sl.Index(i).Set(slVal.Elem().Convert(fieldConfig.PropConfig.SubType)) + } + } + indirect.FieldByName(field).Set(sl) + } } else { - indirect := reflect.Indirect(val) + if raw == nil || rawVal.IsZero() { + continue + } if indirect.FieldByName(field).Type() == rawVal.Type() { indirect.FieldByName(field).Set(rawVal) } else { diff --git a/decoder_test.go b/decoder_test.go index fb2b200..42fe798 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -137,15 +137,19 @@ type f struct { type a struct { BaseNode - TestField string `gogm:"name=test_field"` - TestTypeDefString tdString `gogm:"name=test_type_def_string"` - TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` - SingleA *b `gogm:"direction=incoming;relationship=test_rel"` - ManyA []*b `gogm:"direction=incoming;relationship=testm2o"` - MultiA []*b `gogm:"direction=incoming;relationship=multib"` - SingleSpecA *c `gogm:"direction=outgoing;relationship=special_single"` - MultiSpecA []*c `gogm:"direction=outgoing;relationship=special_multi"` - Created time.Time `gogm:"name=created"` + PropTest0 map[string]interface{} `gogm:"properties;name=props0"` + PropTest1 map[string]string `gogm:"properties;name=props1"` + PropsTest2 []string `gogm:"properties;name=props2"` + PropsTest3 []int `gogm:"properties;name=props3"` + TestField string `gogm:"name=test_field"` + TestTypeDefString tdString `gogm:"name=test_type_def_string"` + TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` + SingleA *b `gogm:"direction=incoming;relationship=test_rel"` + ManyA []*b `gogm:"direction=incoming;relationship=testm2o"` + MultiA []*b `gogm:"direction=incoming;relationship=multib"` + SingleSpecA *c `gogm:"direction=outgoing;relationship=special_single"` + MultiSpecA []*c `gogm:"direction=outgoing;relationship=special_multi"` + Created time.Time `gogm:"name=created"` } type b struct { @@ -203,9 +207,12 @@ func (c *c) SetEndNode(v interface{}) error { } type propsTest struct { - Id int64 `gogm:"name=id"` - UUID string `gogm:"pk;name=uuid"` - Props map[string]interface{} `gogm:"name=props;properties"` + Id int64 `gogm:"name=id"` + UUID string `gogm:"pk;name=uuid"` + PropTest0 map[string]interface{} `gogm:"properties;name=props0"` + PropTest1 map[string]string `gogm:"properties;name=props1"` + PropsTest2 []string `gogm:"properties;name=props2"` + PropsTest3 []int `gogm:"properties;name=props3"` } func TestDecode(t *testing.T) { @@ -647,10 +654,12 @@ func TestInnerDecode(t *testing.T) { id: 1, labels: []string{"propsTest"}, props: map[string]interface{}{ - "uuid": var5uuid, - "props.test.test": "test", - "props.test2": "test2", - "props.test3": "test3", + "uuid": var5uuid, + "props0.test.test": "test", + "props0.test2": 1, + "props1.test": "test", + "props2": []string{"test"}, + "props3": []int{1, 2}, }, }, }, @@ -663,19 +672,25 @@ func TestInnerDecode(t *testing.T) { r := propsTest{ Id: 1, UUID: var5uuid, - Props: map[string]interface{}{ + PropTest0: map[string]interface{}{ "test.test": "test", - "test2": "test2", - "test3": "test3", + "test2": 1, }, + PropTest1: map[string]string{ + "test": "test", + }, + PropsTest2: []string{"test"}, + PropsTest3: []int{1, 2}, } req.Nil(innerDecode(vars5, &readin5)) req.EqualValues(r.Id, readin5.Id) req.EqualValues(r.UUID, readin5.UUID) - req.EqualValues(r.Props["test"], readin5.Props["test"]) - req.EqualValues(r.Props["test2"], readin5.Props["test2"]) - req.EqualValues(r.Props["test3"], readin5.Props["test3"]) + req.EqualValues(r.PropTest0["test"], readin5.PropTest0["test"]) + req.EqualValues(r.PropTest0["test2"], readin5.PropTest0["test2"]) + req.EqualValues(r.PropTest1["test"], readin5.PropTest1["test"]) + req.EqualValues(r.PropsTest2, readin5.PropsTest2) + req.EqualValues(r.PropsTest3, readin5.PropsTest3) //multi single vars6 := [][]interface{}{ diff --git a/decorator.go b/decorator.go index c82a328..52021a4 100644 --- a/decorator.go +++ b/decorator.go @@ -60,7 +60,7 @@ const ( //specifies is the field is a primary key primaryKeyField = "pk" - //specifies if the field is map of type `map[string]interface{}` + //specifies if the field is map of type `map[string]interface{} or []` propertiesField = "properties" //specifies if the field is to be ignored @@ -73,6 +73,13 @@ const ( assignmentOperator = "=" ) +type propConfig struct { + // IsMap if false assume slice + IsMap bool + IsMapSlice bool + SubType reflect.Type +} + //decorator config defines configuration of GoGM field type decoratorConfig struct { // holds reflect type for the field @@ -97,6 +104,8 @@ type decoratorConfig struct { PrimaryKey bool `json:"primary_key"` // specify if the field holds properties Properties bool `json:"properties"` + + PropConfig *propConfig `json:"prop_config"` // specifies if the field contains time value // IsTime bool `json:"is_time"` // specifies if the field contains a typedef of another type @@ -160,7 +169,6 @@ func (d *decoratorConfig) Validate() error { if d.Ignore { if d.Relationship != "" || d.Unique || d.Index || d.ManyRelationship || d.UsesEdgeNode || d.PrimaryKey || d.Properties || d.Name != d.FieldName { - log.Println(d) return NewInvalidDecoratorConfigError("ignore tag cannot be combined with any other tag", "") } @@ -174,22 +182,41 @@ func (d *decoratorConfig) Validate() error { kind := d.Type.Kind() - //check for valid properties - if kind == reflect.Map || d.Properties { - if !d.Properties { - return NewInvalidDecoratorConfigError("properties must be added to gogm config on field with a map", d.Name) - } - - if kind != reflect.Map || d.Type != reflect.TypeOf(map[string]interface{}{}) { - return NewInvalidDecoratorConfigError("properties must be a map with signature map[string]interface{}", d.Name) - } - + // properties supports map and slices + if (kind == reflect.Map || kind == reflect.Slice) && d.Properties && d.Relationship == "" { if d.PrimaryKey || d.Relationship != "" || d.Direction != 0 || d.Index || d.Unique { return NewInvalidDecoratorConfigError("field marked as properties can only have name defined", d.Name) } - //valid properties - return nil + if kind == reflect.Slice { + sliceType := reflect.SliceOf(d.Type) + sliceKind := sliceType.Elem().Elem().Kind() + if _, err := getPrimitiveType(sliceKind); err != nil && sliceKind != reflect.Interface { + return NewInvalidDecoratorConfigError("property slice not of type ", d.Name) + } + } else if kind == reflect.Map { + // check if the key is a string + if d.Type.Kind() == reflect.String { + return NewInvalidDecoratorConfigError("property map key not of type string", d.Name) + } + + mapType := d.Type.Elem() + mapKind := mapType.Kind() + if mapKind == reflect.Slice { + mapElem := mapType.Elem().Kind() + if _, err := getPrimitiveType(mapElem); err != nil { + return NewInvalidDecoratorConfigError("property map not of type or []", d.Name) + } + } else if _, err := getPrimitiveType(mapKind); err != nil && mapType.Kind() != reflect.Interface { + return NewInvalidDecoratorConfigError("property map not of type or [] or interface{} or []interface{}", d.Name) + } + } else { + return NewInvalidDecoratorConfigError("property muss be map[string] or map[string][] or []primitive", d.Name) + } + } else if d.Properties { + return NewInvalidDecoratorConfigError("property must be map[string] or map[string][] or []primitive", d.Name) + } else if kind == reflect.Map { + return NewInvalidDecoratorConfigError("field with map must be marked as a property", d.Name) } //check if type is pointer @@ -199,7 +226,7 @@ func (d *decoratorConfig) Validate() error { } //check valid relationship - if d.Direction != 0 || d.Relationship != "" || (kind == reflect.Struct && d.Type != timeType) || kind == reflect.Slice { + if d.Direction != 0 || d.Relationship != "" || (kind == reflect.Struct && d.Type != timeType) || (kind == reflect.Slice && !d.Properties) { if d.Relationship == "" { return NewInvalidDecoratorConfigError("relationship has to be defined when creating a relationship", d.FieldName) } @@ -350,6 +377,23 @@ func newDecoratorConfig(decorator, name string, varType reflect.Type) (*decorato toReturn.Ignore = true continue case propertiesField: + conf := propConfig{} + conf.IsMapSlice = false + k := varType.Kind() + if k == reflect.Slice { + conf.IsMap = false + conf.SubType = varType.Elem() + } else if k == reflect.Map { + conf.IsMap = true + sub := varType.Elem() + if sub.Kind() == reflect.Slice { + conf.IsMapSlice = true + conf.SubType = sub.Elem() + } else { + conf.SubType = sub + } + } + toReturn.PropConfig = &conf toReturn.Properties = true continue case indexField: diff --git a/decorator_test.go b/decorator_test.go index a0402e7..9fdb8a1 100644 --- a/decorator_test.go +++ b/decorator_test.go @@ -296,6 +296,11 @@ func TestNewDecoratorConfig(t *testing.T) { Properties: true, Name: "test", Type: reflect.TypeOf(map[string]interface{}{}), + PropConfig: &propConfig{ + IsMap: true, + IsMapSlice: false, + SubType: emptyInterfaceType, + }, } compare, err = newDecoratorConfig(decProps, "", reflect.TypeOf(map[string]interface{}{})) @@ -332,14 +337,18 @@ type embedTest struct { type validStruct struct { embedTest - IndexField string `gogm:"index;name=index_field"` - UniqueField int `gogm:"unique;name=unique_field"` - OneToOne *validStruct `gogm:"relationship=one2one;direction=incoming"` - ManyToOne []*a `gogm:"relationship=many2one;direction=outgoing"` - SpecialOne *c `gogm:"relationship=specC;direction=outgoing"` - SpecialMany []*c `gogm:"relationship=manyC;direction=outgoing"` - Props map[string]interface{} `gogm:"properties;name=props"` - IgnoreMe int `gogm:"-"` + IndexField string `gogm:"index;name=index_field"` + UniqueField int `gogm:"unique;name=unique_field"` + OneToOne *validStruct `gogm:"relationship=one2one;direction=incoming"` + ManyToOne []*a `gogm:"relationship=many2one;direction=outgoing"` + SpecialOne *c `gogm:"relationship=specC;direction=outgoing"` + SpecialMany []*c `gogm:"relationship=manyC;direction=outgoing"` + PropsMapInterface map[string]interface{} `gogm:"properties;name=props1"` + PropsMapPrimitive map[string]int `gogm:"properties;name=props2"` + PropsMapSlicePrimitive map[string][]int `gogm:"properties;name=props3"` + PropsSliceInterface []string `gogm:"properties;name=props4"` + PropsPrimitive []int `gogm:"properties;name=props5"` + IgnoreMe int `gogm:"-"` } func (v *validStruct) GetId() int64 { @@ -395,7 +404,9 @@ type invalidStructProperties struct { Id int64 `gogm:"name=id"` UUID string `gogm:"pk;name=uuid"` - Props map[string]string `gogm:"name=props"` //should have properties decorator + Props map[string]*validStruct `gogm:"properties;name=props"` + Props1 map[string][]*validStruct `gogm:"properties;name=props1"` + Props2 []*validStruct `gogm:"properties;name=props2"` } func (i *invalidStructProperties) GetLabels() []string { @@ -507,11 +518,60 @@ func TestGetStructDecoratorConfig(t *testing.T) { ManyRelationship: true, Type: reflect.TypeOf([]*a{}), }, - "Props": { - FieldName: "Props", + "PropsMapInterface": { + FieldName: "PropsMapInterface", Properties: true, - Name: "props", + Name: "props1", Type: reflect.TypeOf(map[string]interface{}{}), + PropConfig: &propConfig{ + IsMap: true, + IsMapSlice: false, + SubType: emptyInterfaceType, + }, + }, + "PropsMapPrimitive": { + FieldName: "PropsMapPrimitive", + Properties: true, + Name: "props2", + Type: reflect.TypeOf(map[string]int{}), + PropConfig: &propConfig{ + IsMap: true, + IsMapSlice: false, + SubType: reflect.TypeOf(int(0)), + }, + }, + "PropsMapSlicePrimitive": { + FieldName: "PropsMapSlicePrimitive", + Properties: true, + Name: "props3", + Type: reflect.TypeOf(map[string][]int{}), + PropConfig: &propConfig{ + IsMap: true, + IsMapSlice: true, + SubType: reflect.TypeOf(int(0)), + }, + }, + "PropsSliceInterface": { + FieldName: "PropsSliceInterface", + Properties: true, + Name: "props4", + Type: reflect.TypeOf([]string{}), + PropConfig: &propConfig{ + IsMap: false, + IsMapSlice: false, + SubType: reflect.TypeOf(""), + }, + }, + "PropsPrimitive": { + FieldName: "PropsPrimitive", + Properties: true, + Name: "props5", + Type: reflect.TypeOf([]int{}), + PropConfig: &propConfig{ + IsMap: false, + IsMapSlice: false, + SubType: reflect.TypeOf(int(0)), + }, }, "IgnoreMe": { FieldName: "IgnoreMe", diff --git a/examples/example.go b/examples/example.go index a4cb6aa..c68d141 100644 --- a/examples/example.go +++ b/examples/example.go @@ -15,14 +15,16 @@ type VertexA struct { // provides required node fields gogm.BaseNode - TestField string `gogm:"name=test_field"` - TestTypeDefString tdString `gogm:"name=test_type_def_string"` - TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` - SingleA *VertexB `gogm:"direction=incoming;relationship=test_rel"` - ManyA []*VertexB `gogm:"direction=incoming;relationship=testm2o"` - MultiA []*VertexB `gogm:"direction=incoming;relationship=multib"` - SingleSpecA *EdgeC `gogm:"direction=outgoing;relationship=special_single"` - MultiSpecA []*EdgeC `gogm:"direction=outgoing;relationship=special_multi"` + TestField string `gogm:"name=test_field"` + TestTypeDefString tdString `gogm:"name=test_type_def_string"` + TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` + MapProperty map[string]string `gogm:"name=map_property;properties"` + SliceProperty []string `gogm:"name=slice_property;properties"` + SingleA *VertexB `gogm:"direction=incoming;relationship=test_rel"` + ManyA []*VertexB `gogm:"direction=incoming;relationship=testm2o"` + MultiA []*VertexB `gogm:"direction=incoming;relationship=multib"` + SingleSpecA *EdgeC `gogm:"direction=outgoing;relationship=special_single"` + MultiSpecA []*EdgeC `gogm:"direction=outgoing;relationship=special_multi"` } type VertexB struct { diff --git a/integration_test.go b/integration_test.go index be61a3b..6b6f2e8 100644 --- a/integration_test.go +++ b/integration_test.go @@ -68,6 +68,18 @@ func TestRawQuery(t *testing.T) { req.NotEmpty(raw) } +type propTest struct { + BaseNode + + MapInterface map[string]interface{} `gogm:"name=prop1;properties"` + MapPrim map[string]string `gogm:"name=prop2;properties"` + MapTdPrim map[string]tdString `gogm:"name=prop3;properties"` + MapSlicePrim map[string][]string `gogm:"name=prop4;properties"` + MapSliceTdPrim map[string][]tdString `gogm:"name=prop5;properties"` + SlicePrim []string `gogm:"name=prop6;properties"` + SliceTdPrim []tdString `gogm:"name=prop7;properties"` +} + func TestIntegration(t *testing.T) { if testing.Short() { t.Skip() @@ -85,7 +97,7 @@ func TestIntegration(t *testing.T) { IndexStrategy: IGNORE_INDEX, } - req.Nil(Init(&conf, &a{}, &b{}, &c{})) + req.Nil(Init(&conf, &a{}, &b{}, &c{}, &propTest{})) log.Println("opening session") @@ -118,6 +130,15 @@ func testSave(sess *Session, req *require.Assertions) { req.Nil(sess.Begin()) a2 := &a{ TestField: "test", + PropTest0: map[string]interface{}{ + "test.test": "test", + "test2": 1, + }, + PropTest1: map[string]string{ + "test": "test", + }, + PropsTest2: []string{"test", "test"}, + PropsTest3: []int{1, 2}, } b2 := &b{ @@ -188,4 +209,39 @@ func testSave(sess *Session, req *require.Assertions) { req.Nil(sess.Begin()) req.Nil(sess.SaveDepth(singleSave, 1)) req.Nil(sess.Commit()) + + // property test + prop1 := propTest{ + BaseNode: BaseNode{}, + MapInterface: map[string]interface{}{ + "test": int64(1), + }, + MapPrim: map[string]string{ + "test": "test1", + }, + MapTdPrim: map[string]tdString{ + "test": "test2", + }, + MapSlicePrim: map[string][]string{ + "test": {"test1", "test2"}, + }, + MapSliceTdPrim: map[string][]tdString{ + "test": {"test1", "test2"}, + }, + SlicePrim: []string{"test2"}, + SliceTdPrim: []tdString{"test3"}, + } + + req.Nil(sess.SaveDepth(&prop1, 0)) + + var prop2 propTest + req.Nil(sess.Load(&prop2, prop1.UUID)) + + req.EqualValues(prop1.MapInterface, prop2.MapInterface) + req.EqualValues(prop1.MapPrim, prop2.MapPrim) + req.EqualValues(prop1.MapTdPrim, prop2.MapTdPrim) + req.EqualValues(prop1.MapSlicePrim, prop2.MapSlicePrim) + req.EqualValues(prop1.MapSliceTdPrim, prop2.MapSliceTdPrim) + req.EqualValues(prop1.SlicePrim, prop2.SlicePrim) + req.EqualValues(prop1.SliceTdPrim, prop2.SliceTdPrim) } diff --git a/save.go b/save.go index 8747861..92c75b3 100644 --- a/save.go +++ b/save.go @@ -322,6 +322,8 @@ func createNodes(runFunc neoRunFunc, crNodes map[string]map[string]nodeCreateCon }) if err != nil { return nil, err + } else if res.Err() != nil { + return nil, res.Err() } for res.Next() { diff --git a/util.go b/util.go index 9cf70f3..29bd78b 100644 --- a/util.go +++ b/util.go @@ -130,28 +130,27 @@ func toCypherParamsMap(val reflect.Value, config structDecoratorConfig) (map[str continue } + field := val.FieldByName(conf.FieldName) + if conf.Properties { //check if field is a map - if conf.Type.Kind() == reflect.Map { - //try to cast it - propsMap, ok := val.FieldByName(conf.FieldName).Interface().(map[string]interface{}) - if ok { - //if it works, create the fields - for k, v := range propsMap { - ret[conf.Name+"."+k] = v - } - } else { - return nil, errors.New("unable to convert map to map[string]interface{}") + if conf.Type.Kind() == reflect.Map && field.Kind() == reflect.Map { + for _, e := range field.MapKeys() { + v := field.MapIndex(e) + es := e.Interface().(string) + ret[conf.Name+"."+es] = v.Interface() } + } else if conf.Type.Kind() == reflect.Slice && field.Kind() == reflect.Slice { + ret[conf.Name] = field.Interface() } else { - return nil, errors.New("properties type is not a map") + return nil, fmt.Errorf("properties type is not a map or slice, %T", field.Interface()) } } else { //check if field is type aliased if conf.IsTypeDef { - ret[conf.Name] = val.FieldByName(conf.FieldName).Convert(conf.TypedefActual).Interface() + ret[conf.Name] = field.Convert(conf.TypedefActual).Interface() } else { - ret[conf.Name] = val.FieldByName(conf.FieldName).Interface() + ret[conf.Name] = field.Interface() } } } diff --git a/util_test.go b/util_test.go index f3900eb..a9a2939 100644 --- a/util_test.go +++ b/util_test.go @@ -108,15 +108,20 @@ func TestToCypherParamsMap(t *testing.T) { "test_type_def_int": 0, "test_type_def_string": "", "test_field": "testvalue", + "props2": []string(nil), + "props3": []int(nil), "created": time.Time{}, }, params) p := propsTest{ Id: 1, UUID: "testuuid", - Props: map[string]interface{}{ + PropTest0: map[string]interface{}{ "test": "testvalue", }, + PropTest1: nil, + PropsTest2: nil, + PropsTest3: nil, } config, err = getStructDecoratorConfig(&p, mappedRelations) @@ -125,8 +130,10 @@ func TestToCypherParamsMap(t *testing.T) { params, err = toCypherParamsMap(reflect.ValueOf(&p), *config) require.Nil(t, err) require.EqualValues(t, map[string]interface{}{ - "uuid": "testuuid", - "props.test": "testvalue", + "uuid": "testuuid", + "props0.test": "testvalue", + "props2": []string(nil), + "props3": []int(nil), }, params) }