Search code examples
dictionarygogenericsinterface

Generic function to set field of different structs used as map values


Having structures with common fields...

type Definition struct {
        Id string
        ...
}
type Requirement struct {
        Id string
        ...
}
type Campaign struct {
        Id string
        ...
}

...I have multiple functions like this:

func fillDefinitionIds(values *map[string]Definition) {           
        for key, value:=range *values { // Repeated code
                value.Id=key            // Repeated code
                (*values)[key]=value    // Repeated code
        }                               // Repeated code
}
func fillRequirementIds(values *map[string]Requirement) {           
        for key, value:=range *values { // Repeated code
                value.Id=key            // Repeated code
                (*values)[key]=value    // Repeated code
        }                               // Repeated code
}
func fillCampaignIds(values *map[string]Campaign) {           
        for key, value:=range *values { // Repeated code
                value.Id=key            // Repeated code
                (*values)[key]=value    // Repeated code
        }                               // Repeated code
}

I would like to have a single function, generalizing the access with generics (or interfaces, whatever), kind of...

func fillIds[T Definition|Requirement|Campaign](values *map[string]T) {           
        for key, value:=range *values {
                value.Id=key
                (*values)[key]=value
        }                                
}

Of course, this gives value.Id undefined (type T has no field or method Id). I've been able many times to overcome similar issues, but this time I can't find a solution for this.

How can be this set of functions be abstracted as a single one?


Solution

  • type Definition struct {
        Id string
    }
    type Requirement struct {
        Id string
    }
    type Campaign struct {
        Id string
    }
    
    func (v Definition) WithId(id string) Definition   { v.Id = id; return v }
    func (v Requirement) WithId(id string) Requirement { v.Id = id; return v }
    func (v Campaign) WithId(id string) Campaign       { v.Id = id; return v }
    
    type WithId[T any] interface {
        WithId(id string) T
    }
    
    func fillIds[T WithId[T]](values map[string]T) {
        for key, value := range values {
            values[key] = value.WithId(key)
        }
    }
    
    func main() {
        m1 := map[string]Definition{"foo": {}, "bar": {}}
        fillIds(m1)
        fmt.Println(m1)
    
        m2 := map[string]Campaign{"abc": {}, "def": {}}
        fillIds(m2)
        fmt.Println(m2)
    }
    

    https://go.dev/play/p/F3Qk0gcyKEa


    An alternative to @blackgreen's answer if using a map of values is a requirement.

    type Common struct {
        Id string
    }
    
    func (v *Common) SetId(id string) { v.Id = id }
    
    type Definition struct {
        Common
    }
    type Requirement struct {
        Common
    }
    type Campaign struct {
        Common
    }
    
    
    type IdSetter[T any] interface {
        *T
        SetId(id string)
    }
    
    func fillIds[T any, U IdSetter[T]](values map[string]T) {
        for key, value := range values {
            U(&value).SetId(key)
            values[key] = value
        }
    }
    
    func main() {
        m1 := map[string]Definition{"foo": {}, "bar": {}}
        fillIds(m1)
        fmt.Println(m1)
    
        m2 := map[string]Campaign{"abc": {}, "def": {}}
        fillIds(m2)
        fmt.Println(m2)
    }
    

    https://go.dev/play/p/AG050b0peFw