简体   繁体   中英

Dynamic struct in parameter in golang

I have to process many files which distinct domain but all of them have line fixed positions

I create structs for domains with tags for start position and end position in line

type IP0059T1 struct {
    TableID                            string `startpos:"1" endpos:"8"`
    EffectiveDateTime                  string `startpos:"11" endpos:"11"`
}

type IP0059T2 struct {
    TableID                            string `startpos:"1" endpos:"8"`
    SequenceNumber                  string `startpos:"11" endpos:"14"`
}

And I create a method, which work ok for one table

func (s *Service) GetIP0059T01(bundleURI string) ([]IP0059T1, error) {
    reader := getReader(bundleURI)

    var items []IP0059T1
    err = patterns.Each(reader, func(e *contract.Entry) error {
        line := string(e.Data)
        var item = new(IP0059T1)
        structName := reflect.TypeOf(item).Name()
        structValues := reflect.ValueOf(item).Elem()

        for i := 0; i < structValues.NumField(); i++ { // iterates through every struct type field
            field := structValues.Field(i) // returns the content of the struct type field
            value, _ := getValue(line, structValues.Type().Field(i), structName)
            _ = s.SetValue(field, structValues, i, structName, value)
        }

        items = append(items, *item)
        return nil
    })
    if err != nil {
        return nil, err
    }
    return items, nil
}

setValue use reflection

func setValue(field reflect.Value, structValues reflect.Value, i int, structName string, value string) error {
    if field.Kind() != reflect.String {
        return &FieldNotStringError{Field: structValues.Type().Field(i).Name, Struct: structName}
    }
    field.SetString(value)
    return nil
}

Also getValue use reflection

func getValue(line string, field reflect.StructField, structName string) (string, error) {
    startPosition, _ := strconv.Atoi(field.Tag.Get("startpos"))
    endPosition, _ := strconv.Atoi(field.Tag.Get("endpos"))

    return line[(startPosition - 1):(endPosition)], nil
}

So, is any workaroud to convert the method GetIP0059T01 to a generic method with get the uri and the type, and return an array of interface which can be transformed to the array of the type I pass? Basically, I want something like a generic

It looks like the only two places where you really need the type is when you allocate it and when you append it to the array. So, you can change the function to get two callbacks:

func GetStuff(bundleURI string, newItem func() interface{},collect func(interface{})) error {

   // instead of var item = new(IP0059T1)
   var item = newItem()

   ...

   // instead of items = append(items, *item)
   collect(item)


}

And call the function with:

items:=make([]Item,0)
GetStuff(uri, func() interface{} {return new(Item)}, 
func(item interface{}) {items=append(items,*item.(*Item))})

You can do it the same way the Go standard library does it, eg in encoding/json , which is to take in the value to be filled in as a parameter. Your "generic" would be something like:

GetValues(bundleURI string, dest interface{}) (error)

Where dest is expected to be a pointer to a slice of whatever type is supposed to be deserialized into, eg:

var v []IP0059T1
err = GetValues(myURI, &v)

Then in GetValues you can use reflect.Type.Elem() to get the type of the slice elements, reflect.New() to create new instances of that type, and reflect.Append() to append them directly into dest . This retains type safety while allowing some measure of generic programming.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM