Skip to content

Commit

Permalink
support struct pointers in maps
Browse files Browse the repository at this point in the history
  • Loading branch information
zevdg committed Sep 8, 2019
1 parent 5a79628 commit e92cdd2
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 44 deletions.
32 changes: 22 additions & 10 deletions fcf.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func (v Value) Decode(u interface{}) error {
return unmarshal(reflect.Indirect(reflect.ValueOf(v)).FieldByName("Fields"), root{reflect.Indirect(reflect.ValueOf(u))})
}

var byteSlice []byte
var byteSliceType = reflect.TypeOf(byteSlice)

func assertTypeMatch(userType reflect.Type, fcfType string) error {
userKind := userType.Kind()
if userKind == reflect.Interface {
Expand All @@ -51,9 +54,9 @@ func assertTypeMatch(userType reflect.Type, fcfType string) error {
}
return fmt.Errorf("type mismatch: Cannot unmarshal firestore values into non-empty interface: %v", userType)
}

var byteSlice []byte
byteSliceType := reflect.TypeOf(byteSlice)
if userKind == reflect.Ptr {
userKind = userType.Elem().Kind()
}

if (fcfType == "integerValue" && reflect.Int <= userKind && userKind <= reflect.Float64) ||
(fcfType == "doubleValue" && (userKind == reflect.Float32 || userKind == reflect.Float64)) ||
Expand Down Expand Up @@ -137,7 +140,11 @@ func (f mapField) getOrInit() reflect.Value {
if v.IsValid() {
return v
}
f.Set(reflect.Zero(f.Type()))
if f.Type().Kind() == reflect.Ptr {
f.Set(reflect.New(f.Type().Elem()))
} else {
f.Set(reflect.Zero(f.Type()))
}
return f.parent.MapIndex(f.key)
}

Expand Down Expand Up @@ -263,6 +270,7 @@ func getSliceFields(fcfVal reflect.Value, uVal fieldBag) (fields []field, err er
}

func getStructFields(fcfVal reflect.Value, usrVal reflect.Value, parentName string) (fields []field) {
usrVal = reflect.Indirect(usrVal)
for i := 0; i < usrVal.Type().NumField(); i++ {
fieldMeta := usrVal.Type().Field(i)
key := fieldMeta.Name
Expand Down Expand Up @@ -299,26 +307,30 @@ func getStructFields(fcfVal reflect.Value, usrVal reflect.Value, parentName stri

func getMapFields(fcfVal reflect.Value, uVal fieldBag) (fields []field, err error) {
usrVal, parentName := uVal.getOrInit(), uVal.Name()
kind := usrVal.Kind()
if kind == reflect.Ptr {
kind = usrVal.Elem().Kind()

if !((usrVal.Kind() == reflect.Interface && usrVal.Type().NumMethod() == 0) ||
usrVal.Kind() == reflect.Struct ||
usrVal.Kind() == reflect.Map) {
typeStr := usrVal.Kind().String()
}
if !((kind == reflect.Interface && usrVal.Type().NumMethod() == 0) ||
kind == reflect.Struct ||
kind == reflect.Map) {
typeStr := kind.String()
if usrVal.IsValid() {
typeStr = usrVal.Type().String()
}
return nil, fmt.Errorf("Can only unmarshal object/map types into Struct, Map, or empty interface fields, not %v", typeStr)
}

if usrVal.Kind() == reflect.Struct {
if kind == reflect.Struct {
// get fields from usrVal
return getStructFields(fcfVal, usrVal, parentName), nil
}

// usrVal is Map, Slice, or empty interface
// get fields from fcfVal
var mapType reflect.Type
if usrVal.Kind() == reflect.Interface {
if kind == reflect.Interface {
var x map[string]interface{}
mapType = reflect.TypeOf(x)
} else {
Expand Down
75 changes: 41 additions & 34 deletions fcf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,10 +771,10 @@ func TestMapNestedOuterDynamic(t *testing.T) {
fcfVal := nestedTestVal(testInner)

userVal := &struct {
O1 interface{} `fcf:"Outer"`
O2 map[string]interface{} `fcf:"Outer"`
O3 map[string]map[string]interface{} `fcf:"Outer"`
O4 map[string]map[string]string `fcf:"Outer"`
M1 interface{} `fcf:"Outer"`
M2 map[string]interface{} `fcf:"Outer"`
M3 map[string]map[string]interface{} `fcf:"Outer"`
M4 map[string]map[string]string `fcf:"Outer"`
}{}
err := fcfVal.Decode(userVal)
if err != nil {
Expand All @@ -785,24 +785,24 @@ func TestMapNestedOuterDynamic(t *testing.T) {
"Inner": testInner,
}

compareOuterMap(t, "O1", testOuter, userVal.O1.(map[string]interface{}))
compareOuterMap(t, "O2", testOuter, userVal.O2)
compareOuterMap(t, "M1", testOuter, userVal.M1.(map[string]interface{}))
compareOuterMap(t, "M2", testOuter, userVal.M2)
// convert to map[string]interface{}
o3 := map[string]interface{}{}
for k, v := range userVal.O3 {
for k, v := range userVal.M3 {
o3[k] = v
}
compareOuterMap(t, "O3", testOuter, o3)
compareOuterMap(t, "M3", testOuter, o3)
// convert to map[string]interface{}
o4 := map[string]interface{}{}
for k1, v1 := range userVal.O4 {
for k1, v1 := range userVal.M4 {
inner := map[string]interface{}{}
for k2, v2 := range v1 {
inner[k2] = v2
}
o4[k1] = inner
}
compareOuterMap(t, "O4", testOuter, o4)
compareOuterMap(t, "M4", testOuter, o4)
}

// func dump(prefix string, v interface{}) {
Expand Down Expand Up @@ -842,30 +842,37 @@ func TestMapNestedOuterDynamic(t *testing.T) {
// dump("O5", userVal.O5)
// }

// func TestMapNestedOuterDynamicInnerStaticPtr(t *testing.T) {
func TestMapNestedOuterDynamicInnerStaticPtr(t *testing.T) {

// key0, key1, key2 := "Elem0", "Elem1", "Elem2"
// val0, val1, val2 := "foo", "bar", "baz"

// fcfVal := nestedTestVal(
// map[string]string{
// key0: val0,
// key1: val1,
// key2: val2,
// })
key0, key1, key2 := "Elem0", "Elem1", "Elem2"
val0, val1, val2 := "foo", "bar", "baz"

// type elems struct {
// Elem0 string
// Elem1 string
// Elem2 string
// }
// userVal := &struct {
// O6 map[string]*elems `fcf:"Outer"`
// }{}
// err := fcfVal.Decode(userVal)
// if err != nil {
// t.Fatal(err)
// }
fcfVal := nestedTestVal(
map[string]string{
key0: val0,
key1: val1,
key2: val2,
})

// dump("O6", userVal.O6)
// }
type elems struct {
Elem0 string
Elem1 string
Elem2 string
}
userVal := &struct {
M map[string]*elems `fcf:"Outer"`
}{}
err := fcfVal.Decode(userVal)
if err != nil {
t.Fatal(err)
}
if val0 != userVal.M["Inner"].Elem0 {
t.Errorf("M[\"Inner\"].%s: expected %q, got %q", key0, val0, userVal.M["Inner"].Elem0)
}
if val1 != userVal.M["Inner"].Elem1 {
t.Errorf("M[\"Inner\"].%s: expected %q, got %q", key1, val1, userVal.M["Inner"].Elem1)
}
if val2 != userVal.M["Inner"].Elem2 {
t.Errorf("M[\"Inner\"].%s: expected %q, got %q", key2, val2, userVal.M["Inner"].Elem2)
}
}

0 comments on commit e92cdd2

Please sign in to comment.