diff options
| author | Shulhan <ms@kilabit.info> | 2022-06-06 00:41:57 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2022-06-08 23:14:42 +0700 |
| commit | 78bcc4bd3556b89f76ac75bf12082ee1c2b4c422 (patch) | |
| tree | e28054e756ce7f6b31292fc2824baf1f6c98c7a8 | |
| parent | 24601d8efe9e604d678aa00af1b6f69ae9d01020 (diff) | |
| download | pakakeh.go-78bcc4bd3556b89f76ac75bf12082ee1c2b4c422.tar.xz | |
lib/reflect: implement Set function to set reflect.Value by string
The Set function set the obj value by converting the string val from
parameter to the obj type.
If the obj is an interface or struct, its value will be set by calling
Unmarshal.
It will return an error if,
- obj is not setable, variable is passed without pointer or pointer
not initialized.
- val is overflow
- obj Kind is Invalid, Array, Chan, Func, Map, or UnsafePointer.
| -rw-r--r-- | lib/reflect/example_test.go | 271 | ||||
| -rw-r--r-- | lib/reflect/reflect.go | 267 | ||||
| -rw-r--r-- | lib/reflect/reflect_test.go | 75 |
3 files changed, 609 insertions, 4 deletions
diff --git a/lib/reflect/example_test.go b/lib/reflect/example_test.go index 607e0acd..6ebee4d6 100644 --- a/lib/reflect/example_test.go +++ b/lib/reflect/example_test.go @@ -74,12 +74,278 @@ func ExampleIsNil() { // <nil>: v == nil is true, IsNil() is true } +func ExampleSet_bool() { + type Bool bool + + var ( + err error + vbool bool + mybool Bool + ) + + err = Set(reflect.ValueOf(&vbool), "YES") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println("YES:", vbool) + } + + err = Set(reflect.ValueOf(&vbool), "TRUE") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println("TRUE:", vbool) + } + + err = Set(reflect.ValueOf(&vbool), "False") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println("False:", vbool) + } + + err = Set(reflect.ValueOf(&vbool), "1") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println("1:", vbool) + } + + err = Set(reflect.ValueOf(&mybool), "true") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println("true:", mybool) + } + + //Output: + //YES: true + //TRUE: true + //False: false + //1: true + //true: true +} + +func ExampleSet_float() { + type myFloat float32 + + var ( + vf32 float32 + myfloat myFloat + err error + ) + + err = Set(reflect.ValueOf(&vf32), "1.223") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vf32) + } + + err = Set(reflect.ValueOf(&myfloat), "999.999") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(myfloat) + } + + //Output: + //1.223 + //999.999 +} + +func ExampleSet_int() { + type myInt int + + var ( + vint int + vint8 int8 + vint16 int16 + vmyint myInt + err error + ) + + err = Set(reflect.ValueOf(&vint), "") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vint) + } + + err = Set(reflect.ValueOf(&vint), "1") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vint) + } + + err = Set(reflect.ValueOf(&vint8), "-128") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vint8) + } + + // Value of int16 is overflow. + err = Set(reflect.ValueOf(&vint16), "32768") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vint16) + } + + err = Set(reflect.ValueOf(&vmyint), "32768") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Println(vmyint) + } + + //Output: + //0 + //1 + //-128 + //error: Set: int16 value is overflow: 32768 + //32768 +} + +func ExampleSet_sliceByte() { + type myBytes []byte + + var ( + vbytes []byte + vmyBytes myBytes + err error + ) + + err = Set(reflect.ValueOf(vbytes), "Show me") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vbytes) + } + + err = Set(reflect.ValueOf(&vbytes), "a hero") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vbytes) + } + + err = Set(reflect.ValueOf(&vbytes), "") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vbytes) + } + + err = Set(reflect.ValueOf(&vmyBytes), "and I will write you a tragedy") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vmyBytes) + } + + //Output: + //error: Set: object []uint8 is not setable + //a hero + // + //and I will write you a tragedy +} + +func ExampleSet_sliceString() { + var ( + vstring []string + err error + ) + + err = Set(reflect.ValueOf(vstring), "Show me") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vstring) + } + + err = Set(reflect.ValueOf(&vstring), "a hero") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vstring) + } + + err = Set(reflect.ValueOf(&vstring), "and I will write you a tragedy") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", vstring) + } + + //Output: + //error: Set: object []string is not setable + //[a hero] + //[a hero and I will write you a tragedy] +} + +func ExampleSet_unmarshal() { + var ( + rat = big.NewRat(0, 1) + myUrl = &url.URL{} + bigInt = big.NewInt(1) + + err error + ) + + // This Set will call UnmarshalText on big.Rat. + err = Set(reflect.ValueOf(rat), "1.234") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", rat.FloatString(4)) + } + + err = Set(reflect.ValueOf(rat), "") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", rat.FloatString(4)) + } + + // This Set will call UnmarshalBinary on url.URL. + err = Set(reflect.ValueOf(myUrl), "https://kilabit.info") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", myUrl) + } + + // This Set will call UnmarshalJSON. + err = Set(reflect.ValueOf(bigInt), "123_456") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", bigInt) + } + + err = Set(reflect.ValueOf(bigInt), "") + if err != nil { + fmt.Println("error:", err) + } else { + fmt.Printf("%s\n", bigInt) + } + + //Output: + //1.2340 + //0.0000 + //https://kilabit.info + //123456 + //0 +} + func ExampleTag() { type T struct { F1 int `atag:" f1 , opt1 , opt2 ,"` F2 int `atag:", opt1"` F3 int - f4 int } var ( @@ -103,7 +369,6 @@ func ExampleTag() { //"f1" [opt1 opt2 ] true //"F2" [opt1] false //"F3" [] false - //"" [] false } func ExampleUnmarshal_unmarshalBinary() { @@ -167,6 +432,7 @@ func ExampleUnmarshal_unmarshalBinary() { func ExampleUnmarshal_unmarshalText() { var ( vals = [][]byte{ + []byte(""), []byte("123.456"), []byte("123_456"), []byte("123456"), @@ -186,6 +452,7 @@ func ExampleUnmarshal_unmarshalText() { } } //Output: + //0/1 //15432/125 //123456/1 //123456/1 diff --git a/lib/reflect/reflect.go b/lib/reflect/reflect.go index 7a3e3289..8d9362cc 100644 --- a/lib/reflect/reflect.go +++ b/lib/reflect/reflect.go @@ -8,6 +8,7 @@ package reflect import ( "fmt" "reflect" + "strconv" "strings" "unsafe" ) @@ -60,6 +61,263 @@ func IsNil(v interface{}) bool { return v == nil } +// Set the obj value by converting the string val to the obj type. +// +// If the obj type is an interface or struct, its value will be set by calling +// Unmarshal function. +// +// It will return an error if, +// - obj is not setable, variable is passed without pointer or pointer +// not initialized. +// - val is overflow +// - obj Kind is Invalid, Array, Chan, Func, Map, or UnsafePointer. +func Set(obj reflect.Value, val string) (err error) { + var ( + logp = "Set" + objType reflect.Type = obj.Type() + objKind reflect.Kind = obj.Kind() + + objValue reflect.Value + ) + + if objKind != reflect.Ptr { + // Variable passed value (V T). + return fmt.Errorf("%s: object %T is not setable", logp, obj.Interface()) + } + + objValue = obj + obj = obj.Elem() + objType = objType.Elem() + objKind = objType.Kind() + + if objKind == reflect.Ptr { + // Variable is passed as **T. + if obj.IsNil() { + objType = objType.Elem() + objValue = reflect.New(objType) + obj.Set(objValue) + } else { + objValue = obj + } + } else { + if objValue.IsNil() { + // Variable is passed as pointer (V *T) but not + // initialized. + return fmt.Errorf("%s: object %T is not initialized", logp, obj.Interface()) + } + } + + switch objKind { + case reflect.Invalid: + return fmt.Errorf("%s: object %T is invalid", logp, obj) + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + return fmt.Errorf("%s: object %T is not setable", logp, obj.Interface()) + + case reflect.Slice: + objType = objType.Elem() + objKind = objType.Kind() + if objKind == reflect.Uint8 { + err = setValue(objValue, val) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + } else { + obj, err = setSlice(obj, val) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + objValue.Elem().Set(obj) + } + + default: + err = setValue(objValue, val) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + } + return nil +} + +// setSlice append the string value of val to the slice. +func setSlice(slice reflect.Value, val string) (sliceOut reflect.Value, err error) { + var ( + sliceKind reflect.Kind = slice.Kind() + sliceType reflect.Type = slice.Type() + + elValue reflect.Value + ptrValue reflect.Value + ) + + if sliceKind != reflect.Slice { + return sliceOut, fmt.Errorf("expecting slice, got %T", slice.Interface()) + } + + sliceType = sliceType.Elem() // T = []T + sliceKind = sliceType.Kind() + + if sliceKind == reflect.Ptr { + sliceType = sliceType.Elem() + sliceKind = sliceType.Kind() + + if sliceKind == reflect.Ptr { + elValue = reflect.New(sliceType) // var t = new(*T) + sliceType = sliceType.Elem() // *T <= **T + ptrValue = reflect.New(sliceType) // var pt = new(T) + elValue.Elem().Set(ptrValue) // *t = pt + } else { + ptrValue = reflect.New(sliceType) // t = new(T) + elValue = ptrValue + } + } else { + ptrValue = reflect.New(sliceType) // var pt = new(T) + elValue = ptrValue.Elem() // var t = *pt + } + + err = setValue(ptrValue, val) + if err != nil { + return sliceOut, err + } + + sliceOut = reflect.Append(slice, elValue) + + return sliceOut, nil +} + +// setValue the string value val into reflect.Value based on type of object +// pass in objType. +// +// It will return an error if objType Kind is Invalid, Array, Chan, Func, Map, +// Slice, or UnsafePointer. +func setValue(obj reflect.Value, val string) (err error) { + var ( + objType = obj.Type() + objKind = obj.Kind() + + objValue reflect.Value + v interface{} + ) + + for objKind == reflect.Ptr { + objType = objType.Elem() + objKind = objType.Kind() + } + + switch objKind { + case reflect.Bool: + var vbool bool + val = strings.ToLower(val) + if val == "yes" || val == "true" || val == "1" { + vbool = true + } + v = vbool + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var vint64 int64 + + if len(val) != 0 { + vint64, err = strconv.ParseInt(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid integer value: %s", val) + } + if obj.Elem().OverflowInt(vint64) { + return fmt.Errorf("%s value is overflow: %s", objKind, val) + } + } + switch objKind { + case reflect.Int: + v = int(vint64) + case reflect.Int8: + v = int8(vint64) + case reflect.Int16: + v = int16(vint64) + case reflect.Int32: + v = int32(vint64) + default: + v = vint64 + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var vuint64 uint64 + + if len(val) != 0 { + vuint64, err = strconv.ParseUint(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid unsigned integer value: %s", val) + } + if obj.Elem().OverflowUint(vuint64) { + return fmt.Errorf("%s value is overflow: %s", objKind, val) + } + } + switch objKind { + case reflect.Uint: + v = uint(vuint64) + case reflect.Uint8: + v = uint8(vuint64) + case reflect.Uint16: + v = uint16(vuint64) + case reflect.Uint32: + v = uint32(vuint64) + default: + v = vuint64 + } + + case reflect.Float32, reflect.Float64: + var vf64 float64 + + if len(val) != 0 { + vf64, err = strconv.ParseFloat(val, 64) + if err != nil { + return fmt.Errorf("invalid float value: %s", val) + } + if obj.Elem().OverflowFloat(vf64) { + return fmt.Errorf("%s value is overflow: %s", objKind, val) + } + } + if objKind == reflect.Float32 { + var f32 = float32(vf64) + v = f32 + } else { + v = vf64 + } + + case reflect.Slice: + var ( + sliceType = objType.Elem() + sliceKind = sliceType.Kind() + ) + if sliceKind != reflect.Uint8 { + return fmt.Errorf("cannot convert %s to %s", val, objType) + } + v = []byte(val) + + case reflect.String: + v = val + + case reflect.Interface, reflect.Struct: + // If type implement UnmarshalBinary, UnmarshalJSON, or + // UnmarshalText; use it to set the value. + + _, err = Unmarshal(obj, []byte(val)) + if err != nil { + return err + } + return nil + + default: + return fmt.Errorf("cannot convert %s to %s", val, objType) + } + + objValue = reflect.ValueOf(v) + if len(objType.PkgPath()) != 0 { + // Type is not predeclared (builtin). + objValue = objValue.Convert(objType) + } + obj.Elem().Set(objValue) + + return nil +} + // Unmarshal set the obj value by calling one of the method: // UnmarshalBinary, UnmarshalJSON, or UnmarshalText; in respective // order. @@ -91,7 +349,7 @@ func Unmarshal(obj reflect.Value, val []byte) (ok bool, err error) { methodName string ) - if objKind != reflect.Pointer { + if objKind != reflect.Ptr { // Variable passed as is (V T). return false, nil } @@ -101,7 +359,7 @@ func Unmarshal(obj reflect.Value, val []byte) (ok bool, err error) { objType = objType.Elem() objKind = objType.Kind() - if objKind == reflect.Pointer { + if objKind == reflect.Ptr { // Variable is passed as **T. if obj.IsNil() { objType = objType.Elem() @@ -118,6 +376,11 @@ func Unmarshal(obj reflect.Value, val []byte) (ok bool, err error) { } } + if len(val) == 0 { + obj.Set(reflect.Zero(objType)) + return true, nil + } + for _, methodName = range methodNames { method = objValue.MethodByName(methodName) if !method.IsValid() { diff --git a/lib/reflect/reflect_test.go b/lib/reflect/reflect_test.go new file mode 100644 index 00000000..98e65c61 --- /dev/null +++ b/lib/reflect/reflect_test.go @@ -0,0 +1,75 @@ +// Copyright 2022, Shulhan <ms@kilabit.info>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect + +import ( + "reflect" + "testing" +) + +func TestAppendSlice(t *testing.T) { + var ( + v123 int = 123 + v456 int = 456 + pv123 *int = &v123 + pv456 *int = &v456 + + sliceT []int + slicePtrT []*int + slicePtrPtrT []**int + sliceBytes [][]byte + ) + + type testCase struct { + obj interface{} + exp interface{} + desc string + vals []string + } + + var ( + cases = []testCase{{ + desc: "setSlice []int", + obj: sliceT, + exp: []int{v123, v456}, + vals: []string{"123", "456"}, + }, { + desc: "setSlice []*int", + obj: slicePtrT, + exp: []*int{&v123, &v456}, + vals: []string{"123", "456"}, + }, { + desc: "setSlice []**int", + obj: slicePtrPtrT, + exp: []**int{&pv123, &pv456}, + vals: []string{"123", "456"}, + }, { + desc: "setSlice [][]byte", + obj: sliceBytes, + exp: [][]byte{[]byte("123"), []byte("456")}, + vals: []string{"123", "456"}, + }} + + got reflect.Value + c testCase + val string + err error + ) + for _, c = range cases { + t.Log(c.desc) + + got = reflect.ValueOf(c.obj) + for _, val = range c.vals { + got, err = setSlice(got, val) + if err != nil { + t.Fatal(err) + } + } + + if !reflect.DeepEqual(c.exp, got.Interface()) { + t.Fatalf("expecting %v, got %v", c.exp, c.obj) + } + } +} |
