Skip to content

Commit

Permalink
Merge branch '1.1.1'. Fixes #3.
Browse files Browse the repository at this point in the history
  • Loading branch information
livingsilver94 committed Jan 26, 2022
2 parents fd10734 + 942a2cf commit c4cd53f
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 14 deletions.
19 changes: 13 additions & 6 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ type StringMapMarshaler interface {
// from its method along with the error value. When not, Marshal reads every exported field and translates it
// into a (key, value) pair to be added to the resulting map. Interfaces or pointers to struct are also accepted.
//
// Marshal converts all fields of non-reference built-in types except arrays, plus
// Marshal converts all fields with built-in types except arrays, functions and channels, plus
// structs implementing encoding.TextMarshaler or fmt.Stringer, checked in this exact order.
// If a field is a pointer to a supported type, the underlying type's value is marshaled.
// If the pointer is nil, it is marshaled as it had the underlying type's zero value unless `omitempty`
// is specified.
//
// The encoding of each struct field can be customized by the format string stored under the "redmap"
// key in the struct field's tag. The format string gives the name of the field, possibly followed by
Expand All @@ -33,13 +36,13 @@ type StringMapMarshaler interface {
// // Field appears in the map as key "customName".
// Field int `redmap:"customName"`
//
// // Field appears in the map as key "customName" and
// // the field is omitted from the map if its value
// // is empty as defined by the Go language.
// // Field appears in the map as key "customName" unless
// // it has the zero value as defined by the Go specifications.
// // In such case, the field is not added to the map.
// Field int `redmap:"customName,omitempty"`
//
// // Field appears in the map as key "Field" (the default), but
// // the field is skipped if empty. Note the leading comma.
// // the field is skipped if zero. Note the leading comma.
// Field int `redmap:",omitempty"`
//
// // Field is ignored by this package.
Expand Down Expand Up @@ -102,7 +105,7 @@ func marshalRecursive(mp map[string]string, prefix string, stru reflect.Value) e
tags.name = field.Name
}

for value.Kind() == reflect.Ptr {
for value.Kind() == reflect.Ptr && !value.IsNil() {
value = value.Elem()
}

Expand Down Expand Up @@ -134,6 +137,10 @@ func structToMap(mp map[string]string, prefix string, stru reflect.Value) error
}

func fieldToString(val reflect.Value) (string, error) {
for val.Kind() == reflect.Ptr {
underlying := reflect.TypeOf(val.Interface()).Elem()
val = reflect.New(underlying).Elem()
}
typ := val.Type()
if typ.Implements(textMarshalerType) {
str, err := val.Interface().(encoding.TextMarshaler).MarshalText()
Expand Down
22 changes: 20 additions & 2 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func TestMarshalWithTags(t *testing.T) {
}
}

func TestMapMarshaler(t *testing.T) {
func TestMarshalMapMarshaler(t *testing.T) {
tests := []struct {
In redmap.StringMapMarshaler
Out map[string]string
Expand All @@ -230,7 +230,7 @@ func TestMapMarshaler(t *testing.T) {
}
}

func TestInnerMapMarshaler(t *testing.T) {
func TestMarshalInnerMapMarshaler(t *testing.T) {
stru := struct {
RegularField string
Struct stubMapMarshaler `redmap:",inline"`
Expand All @@ -251,3 +251,21 @@ func TestInnerMapMarshaler(t *testing.T) {
t.Fatalf("Marshal's output doesn't respect struct tags\n\tExpected: %v\n\tOut: %v", expected, out)
}
}

func TestMarshalNilField(t *testing.T) {
stru := struct {
Field *int
FieldOmit *int `redmap:",omitempty"`
FieldDouble **int
FieldOmitDouble **int `redmap:",omitempty"`
}{}
expected := map[string]string{"Field": "0", "FieldDouble": "0"}
out, err := redmap.Marshal(stru)
redmap.Marshal(stru)
if err != nil {
t.Fatalf("Marshal returned unexpected error %q", err)
}
if !reflect.DeepEqual(out, expected) {
t.Fatalf("Marshal's output doesn't respect struct tags\n\tExpected: %v\n\tOut: %v", expected, out)
}
}
2 changes: 1 addition & 1 deletion unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func unmarshalRecursive(mp map[string]string, prefix string, stru reflect.Value)
tags.name = prefix + tags.name

for value.Kind() == reflect.Ptr {
if value.IsNil() {
if value.IsNil() && !tags.omitempty {
if !value.CanSet() {
return fmt.Errorf("cannot set embedded pointer to unexported type %s", value.Elem().Type())
}
Expand Down
38 changes: 33 additions & 5 deletions unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ func TestUnmarshalInvalidType(t *testing.T) {
val: func() reflect.Value { return reflect.ValueOf(100) },
expErr: redmap.ErrNotPointer},
{
// Int is not a struct.
// Int is not a struct nor StringMapUnmarshaler.
val: func() reflect.Value {
v := 100
return reflect.ValueOf(&v)
},
expErr: redmap.ErrNoCodec},
{
// Interface is not a struct.
// Interface is not a struct nor StringMapUnmarshaler.
val: func() reflect.Value {
v := fmt.Stringer(stubStringer{})
return reflect.ValueOf(&v)
Expand Down Expand Up @@ -196,7 +196,7 @@ func TestUnmarshalInnerStructs(t *testing.T) {
}
}

func TestUnarshalUnexported(t *testing.T) {
func TestUnmarshalUnexported(t *testing.T) {
mp := map[string]string{"Exp": "atest"}
tests := []struct {
Out interface{}
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestUnmarshalWithTags(t *testing.T) {
}
}

func TestMapUnmarshaler(t *testing.T) {
func TestUnmarshalMapUnmarshaler(t *testing.T) {
intUn := stubIntMapUnmarshaler(666)
tests := []struct {
In map[string]string
Expand All @@ -274,7 +274,7 @@ func TestMapUnmarshaler(t *testing.T) {
}
}

func TestInnerMapUnmarshaler(t *testing.T) {
func TestUnmarshalInnerMapUnmarshaler(t *testing.T) {
tests := []struct {
In map[string]string
Out interface{}
Expand Down Expand Up @@ -304,3 +304,31 @@ func TestInnerMapUnmarshaler(t *testing.T) {
}
}
}

func TestUnmarshalNilField(t *testing.T) {
var (
zero = 0
zeroDouble = &zero
)
type stru struct {
Field *int
FieldOmit *int `redmap:",omitempty"`
FieldDouble **int
FieldOmitDouble **int `redmap:",omitempty"`
}
expected := stru{
Field: &zero,
FieldOmit: nil,
FieldDouble: &zeroDouble,
FieldOmitDouble: nil,
}
mp := map[string]string{"Field": "0", "FieldDouble": "0"}
var out stru
err := redmap.Unmarshal(mp, &out)
if err != nil {
t.Fatalf("Unmarshal returned unexpected error %q", err)
}
if !reflect.DeepEqual(out, expected) {
t.Fatalf("Unmarshal's output doesn't match the expected value\n\tIn: %v\n\tExpected: %v\n\tOut: %v", mp, expected, out)
}
}

0 comments on commit c4cd53f

Please sign in to comment.