gointerfacepolymorphism

How do you use polymorphism with Go maps?


I'm curious on what kind of patterns should used in Go for such cases. So let's suppose, you have a Request struct that holds some information for making an HTTP request, along with the parameters that can be sent in the payload and their respective validations. Now different type of validations would be applicable on the basis of the data type of the parameter, so I decided to use a map[string]Validations, where I could store the type of Validations mentioned in the Validations union (basically, being able to accomodate both IntValidations & StrValidations in the same map).

But the below code would result in an error because Validations can only be used as a type parameter, so how can we achieve this in Go? In Java this could be easily achieved with polymorphism, where Validation would be the parent class and int and float validations would be the child classes, so later on we could easily instantiate a HashMap of Validations.

Note: I would want to avoid the usage of map[string]interface{}.

type Validations interface {
IntValidations | StrValidations
}

type IntValidations struct {
  MaxVal int
  MinVal int
}

type StrValidations struct {
  MinLength int
  MaxLength int
  Regex string 
}

type Request struct {
 Path string 
 Payload map[string]Validations // Error: Validations cannot be used outside of a type constraint.
}

Solution

  • As pointed out by @Cerise Limón in the comments, you can do it like so

    package main
    
    type Validators interface {
        Validate(value any) error // Can return bool also
    }
    
    type IntValidator struct {
        MaxVal int
        MinVal int
    }
    
    func (intValidator *IntValidator) Validate(value any) error {
        // run validations
        return nil
    }
    
    type StrValidator struct {
        MinLength int
        MaxLength int
        Regex     string
    }
    
    func (strValidator *StrValidator) Validate(value any) error {
        // run validations
        return nil
    }
    
    type Request struct {
        Path    string
        Payload map[string]Validations
    }
    

    Now you can add validators with

    func main() {
        request := Request{Payload: make(map[string]Validations)}
        request.Payload["myIntVar"] = &IntValidations{MinVal: 10, MaxVal: 20}
        request.Payload["myStrVar"] = &IntValidations{MinLen: 10, MaxLen: 15, Regex: "[a-z]+"}
    
        if err := request.Payload["myIntVar"].Validate(13); err != nil {
            panic("could not validate")
        }
    }