From 8142192dc3edc2e87ecb1bb5c4dd7e4cef5afa0d Mon Sep 17 00:00:00 2001 From: Oncilla Date: Sat, 25 Apr 2020 21:19:25 +0200 Subject: [PATCH 1/4] unmarshal: support encoding.TextUnmarshaler This PR adds support for decoding fields of primitive types that implement encoding.TextUnmarshaler by calling the custom method. Fields in anonymous structs are not supported at this point. --- marshal.go | 34 +++++++++++++++++++++++ marshal_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/marshal.go b/marshal.go index 9c5957b1..ea8b533a 100644 --- a/marshal.go +++ b/marshal.go @@ -71,6 +71,7 @@ const ( var timeType = reflect.TypeOf(time.Time{}) var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() +var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() var localDateType = reflect.TypeOf(LocalDate{}) var localTimeType = reflect.TypeOf(LocalTime{}) var localDateTimeType = reflect.TypeOf(LocalDateTime{}) @@ -155,6 +156,14 @@ func callTextMarshaler(mval reflect.Value) ([]byte, error) { return mval.Interface().(encoding.TextMarshaler).MarshalText() } +func isTextUnmarshaler(mtype reflect.Type) bool { + return mtype.Implements(textUnmarshalerType) +} + +func callTextUnmarshaler(mval reflect.Value, text []byte) error { + return mval.Interface().(encoding.TextUnmarshaler).UnmarshalText(text) +} + // Marshaler is the interface implemented by types that // can marshal themselves into valid TOML. type Marshaler interface { @@ -866,6 +875,14 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) default: d.visitor.visit() + // Check if pointer to value implements the encoding.TextUnmarshaler. + if mvalPtr := reflect.New(mtype); isTextUnmarshaler(mvalPtr.Type()) && !isTimeType(mtype) { + if err := d.unmarshalText(tval, mvalPtr); err != nil { + return reflect.ValueOf(nil), fmt.Errorf("unmarshal text: %v", err) + } + return mvalPtr.Elem(), nil + } + switch mtype.Kind() { case reflect.Bool, reflect.Struct: val := reflect.ValueOf(tval) @@ -983,6 +1000,23 @@ func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *ref return mval, nil } +func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { + var s string + switch val := tval.(type) { + case string: + s = val + case bool: + s = fmt.Sprintf("%v", val) + case int64: + s = fmt.Sprintf("%d", val) + case float64: + s = fmt.Sprintf("%f", val) + default: + return fmt.Errorf("unspported type: %t", val) + } + return callTextUnmarshaler(mval, []byte(s)) +} + func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { tag := vf.Tag.Get(an.tag) parse := strings.Split(tag, ",") diff --git a/marshal_test.go b/marshal_test.go index 59cafcb2..db29e717 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "reflect" + "strconv" "strings" "testing" "time" @@ -3226,3 +3227,75 @@ func TestDecoderStrictValid(t *testing.T) { t.Fatal("unexpected error:", err) } } + +type intWrapper struct { + Value int +} + +func (w *intWrapper) UnmarshalText(text []byte) error { + var err error + if w.Value, err = strconv.Atoi(string(text)); err == nil { + return nil + } + if b, err := strconv.ParseBool(string(text)); err == nil { + if b { + w.Value = 1 + } + return nil + } + if f, err := strconv.ParseFloat(string(text), 32); err == nil { + w.Value = int(f) + return nil + } + return fmt.Errorf("unsupported: %s", text) +} + +func TestTextUnmarshal(t *testing.T) { + var doc struct { + UnixTime intWrapper + Version *intWrapper + + Bool intWrapper + Int intWrapper + Float intWrapper + } + + input := ` +UnixTime = "12" +Version = "42" +Bool = true +Int = 21 +Float = 2.0 +` + + if err := Unmarshal([]byte(input), &doc); err != nil { + t.Fatalf("unexpected err: %s", err.Error()) + } + if doc.UnixTime.Value != 12 { + + t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) + } + if doc.Version.Value != 42 { + t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) + } + if doc.Bool.Value != 1 { + t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) + } + if doc.Int.Value != 21 { + t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) + } + if doc.Float.Value != 2 { + t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) + } +} + +func TestTextUnmarshalError(t *testing.T) { + var doc struct { + Failer intWrapper + } + + input := `Failer = "hello"` + if err := Unmarshal([]byte(input), &doc); err == nil { + t.Fatalf("expected err, got none") + } +} From 38c78fd86c71d31461dd97aca2e40f483751081b Mon Sep 17 00:00:00 2001 From: Oncilla Date: Wed, 29 Apr 2020 18:01:19 +0200 Subject: [PATCH 2/4] Update marshal.go As suggested by @lmb Co-Authored-By: Lorenz Bauer --- marshal.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/marshal.go b/marshal.go index ea8b533a..d52daf0e 100644 --- a/marshal.go +++ b/marshal.go @@ -1001,7 +1001,9 @@ func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *ref } func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { - var s string + var buf bytes.Buffer + fmt.Fprint(&buf, tval) + return callTextUnmarshaler(mval, buf.Bytes()) switch val := tval.(type) { case string: s = val From 8b9d82dc4942defff67a1ea88509c5f259fce1b2 Mon Sep 17 00:00:00 2001 From: Oncilla Date: Wed, 29 Apr 2020 18:03:30 +0200 Subject: [PATCH 3/4] github ui fail --- marshal.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/marshal.go b/marshal.go index d52daf0e..6ab587ea 100644 --- a/marshal.go +++ b/marshal.go @@ -1004,19 +1004,6 @@ func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { var buf bytes.Buffer fmt.Fprint(&buf, tval) return callTextUnmarshaler(mval, buf.Bytes()) - switch val := tval.(type) { - case string: - s = val - case bool: - s = fmt.Sprintf("%v", val) - case int64: - s = fmt.Sprintf("%d", val) - case float64: - s = fmt.Sprintf("%f", val) - default: - return fmt.Errorf("unspported type: %t", val) - } - return callTextUnmarshaler(mval, []byte(s)) } func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { From 2b9b2f77d81e8e8f7b5db33901c0ac8f60872181 Mon Sep 17 00:00:00 2001 From: Oncilla Date: Wed, 29 Apr 2020 18:05:08 +0200 Subject: [PATCH 4/4] fix spacing --- marshal_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/marshal_test.go b/marshal_test.go index db29e717..4fa96006 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -3272,7 +3272,6 @@ Float = 2.0 t.Fatalf("unexpected err: %s", err.Error()) } if doc.UnixTime.Value != 12 { - t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) } if doc.Version.Value != 42 {