lundi 24 janvier 2022

golang patch string values on an object, recursive with filtering

Community,

The mission

basic

Implement a func that patches all string fields on an objects

details

  • [done] fields shall only be patched if they match a matcher func
  • [done] value shall be processed via process func
  • patching shall be done recursive
  • it shall also work for []string, []*string and recursive for structs and []struct, []*struct

whats working

process strings on structs, if the object is passed via pointer

progress

see code below.
but this seems way too complicated

package main

import (
    "encoding/json"
    "fmt"
    "pmi.at/log"
    "pmi.at/str"
    "reflect"
    "strings"
)

type SOTag struct {
    Name    string  `process:"yes"`
    NamePtr *string `process:"yes"`
}

type SOUser struct {
    ID           int
    Nick         string
    Name         string    `process:"yes"`
    NamePtr      *string   `process:"yes"`
    Slice        []string  `process:"yes"`
    SlicePtr     []*string `process:"yes"`
    SubStruct    []SOTag   `process:"yes"`
    SubStructPtr []*SOTag  `process:"yes"`
}

func main() {
    fmt.Println("try to update string values of a struct via reflection")
    var NamePtr string = "golang"
    testUser := SOUser{
        ID:      1,
        Name:    "lumo",
        NamePtr: &NamePtr,
        SubStruct: []SOTag{SOTag{
            Name: "go",
        },
        },
        SubStructPtr: make([]*SOTag, 0),
    }

    err := patch( // struct to patch
        &testUser,
        // filter - true -> all fields that are strings
        func(idx int, v reflect.Value) bool {
            prefix := "-"
            matches := hasTag(v.Type().Field(idx), "process", "yes")
            if matches {
                prefix = "+"
            }
            fmt.Printf("MATCHER: [%s] %v k:%v t:%v - tag `process:'yes'`\n", prefix, v.Type().Field(idx).Name, v.Field(idx).Kind(), v.Field(idx).Type())
            return matches
        },
        // matcher
        func(in string) string {
            return fmt.Sprintf("!%s!", strings.ToUpper(in))
        })
    if err != nil {
        panic(err)
    }
    fmt.Println("Output:")
    fmt.Println(Stringify(testUser))
}

// patch shall accept i and &i and patch all fields (or *field) recursive via matcher...
func patch(i interface{}, matches func(idx int, v reflect.Value) bool, process func(s string) string) error {
    v := reflect.ValueOf(i)

    // if its a pointer, resolve its value
    if v.Kind() == reflect.Ptr {
        //v = reflect.Indirect(v)
        v = v.Elem()
    }

    // should double-check we now have a struct (could still be anything)
    if v.Kind() != reflect.Struct {
        return fmt.Errorf("unexpected type: %v", v.Kind())
    }
    for idx := 0; idx < v.NumField(); idx++ {
        f := v.Field(idx)
        if matches(idx, v) {
            n := v.Type().Field(idx).Name
            t := f.Type().Name()
            switch f.Kind() {
            case reflect.String:
                fmt.Println("KIND String -> Done?")

                value := f.Interface().(string)
                fmt.Printf("    [READ] Name: %s  Kind: %s  Type: %s Value: %v\n", n, f.Kind(), t, value)
                processedValue := process(f.Interface().(string))
                fmt.Printf("    [PATCH] Name: %s  Kind: %s  Type: %s Value: %v\n", n, f.Kind(), t, processedValue)
                //fmt.Printf("  write the value back - but how??: %v", processedValue)
                setValue(i, n, processedValue)
                fmt.Printf("    [WROTE] %v\n", f.Interface())
            //case reflect.Struct:
            //  fmt.Println("CALL Struct -> REFLECT RECURSIVE")
            //case reflect.Array: ?? equal slice?
            case reflect.Slice:
                fmt.Println("   KIND Slice -> Done(TODO:test)")
                slice := reflect.ValueOf(t)
                typ := reflect.TypeOf(f.Interface()).Elem()

                fmt.Printf("        XXX KIND Slice[]%s\n", typ)
                switch typ.Kind() {
                case reflect.String:
                    for i := 0; i < slice.Len(); i++ {
                        fmt.Println("           *** slice.Index(i) -> ", slice.Index(i))
                        value := f.Interface().(string)
                        fmt.Printf("    [READ] Name: %s  Kind: %s  Type: %s Value: %v\n", n, f.Kind(), t, value)
                        processedValue := process(f.Interface().(string))
                        slice.Index(i).SetString(processedValue)
                    }
                case reflect.Ptr:
                    // remove pointer and process
                    // this seems way too complicated... is there a better way???
                case reflect.Struct:
                    for i := 0; i < slice.Len(); i++ {
                        fmt.Println(slice.Index(i))
                        patch(slice.Index(i), matches, process)
                    }
                }

            case reflect.Ptr:
                fmt.Println("   KIND Ptr -> REFLECT RECURSIVE")
                fmt.Printf("        f.Type() -> %v\n", f.Type())               // prints **Foo
                fmt.Printf("        f.Type().Kind() -> %v\n", f.Type().Kind()) // prints **Foo
                //fmt.Println(" val:", val)
                //patch(val.Interface(), matches, process)
                switch f.Type().Kind() {
                default:
                    // implement for struct -> recursion
                    fmt.Printf("        TYPE %s (not implemented yet) -> REFLECT RECURSIVE\n", f.Type().Kind())

                }
            case reflect.Int:
            case reflect.Int8:
            case reflect.Int16:
            case reflect.Int32:
            case reflect.Int64:
            case reflect.Uint:
            case reflect.Uint8:
            case reflect.Uint16:
            case reflect.Uint32:
            case reflect.Uint64:
            case reflect.Uintptr:
            case reflect.Bool:
            case reflect.Float32:
            case reflect.Float64:
            case reflect.Func:
            case reflect.Chan:
            case reflect.Complex64:
            case reflect.Complex128:
            case reflect.UnsafePointer:
            case reflect.Interface:
            case reflect.Invalid:
                fmt.Printf("    KIND %s (skip)\n", f.Kind())
            default:
                // implement for struct -> recursion
                //TODO: Array
                //TODO: Map
                fmt.Printf("    KIND %s (not implemented yet) -> REFLECT RECURSIVE\n", f.Kind())
            }
        }
    }
    return nil
}

func Stringify(i interface{}) string {
    s, _ := json.MarshalIndent(i, "", " ")
    return string(s)
}

func hasTag(typeField reflect.StructField, tagName string, tagValue string) bool {
    tag := typeField.Tag
    if value, ok := tag.Lookup(tagName); ok {
        return value == tagValue
    }
    return false
}

//setValue
// @param i must be ptr
func setValue(i interface{}, fieldName string, newValue string) {
    log.Tracef("setValue on %v %v->%v", str.Stringify(i, false), fieldName, newValue)

    v := reflect.ValueOf(i)

    // if its a pointer, resolve its value
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    if v.Kind() == reflect.Interface {
        v2 := reflect.ValueOf(v.Interface())
        v2.FieldByName(fieldName).SetString(newValue)
        return
    }

    // should double-check we now have a struct (could still be anything)
    if v.Kind() != reflect.Struct {
        log.Fatalf("unexpected type: %v", v.Kind())
        //return
        v = v.Elem()
    }

    // exported field
    f := v.FieldByName(fieldName)
    if f.IsValid() {
        // A Value can be changed only if it is
        // addressable and was not obtained by
        // the use of unexported struct fields.
        if f.CanSet() {
            // change value of N
            if f.Kind() == reflect.String {
                f.SetString(newValue)
            }
        }
    }
}




Aucun commentaire:

Enregistrer un commentaire