goenumsunmarshallingcustom-typeiota

Unmarshal to Enum type using a custom Scan func


I'm trying to unmarshall a Gender type that is an Enumeration that is just int64 under the hood (I don't want to use any string representation of this type).

Problem is that the Gender value is not handled properly, I always ended with 0.

I'm missing something but I don't see it...

Thank you so much.

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

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
)

type Person struct {
    name   string `json:"name"`
    gender Gender `json:"gender"`
}

type Gender int64

const (
    Undefined Gender = iota
    Male
    Female
    NonBinary
)

func (g *Gender) Scan(v interface{}) error {
    if v == nil {
        *g = Gender(Undefined)
        return nil
    }

    ns := sql.NullInt64{}
    if err := ns.Scan(v); err != nil {
        return err
    }

    if !ns.Valid {
        return fmt.Errorf("Gender.Scan: column is not nullable")
    }

    if ns.Int64 > 3 {
        return fmt.Errorf("Gender.Scan: gender value > 3")
    }
    *g = genderFromInt64(ns.Int64) // tried Gender(ns.Int64) without success
    return nil
}

// tried genderFromInt64(i int64) instead of Gender(ns.Int64) without success
func genderFromInt64(i int64) Gender {
    switch i {
    case 0:
        return Undefined
    case 1:
        return Male
    case 2:
        return Female
    case 3:
        return NonBinary
    default:
        return Female
    }
}

// Value() is not used yet
func (g Gender) Value() (driver.Value, error) {
    return int64(g), nil
}

func (g Gender) String() string {
    return [...]string{"Undefined", "Male", "Female", "Non-binary"}[g]
}

func main() {
    var person Person
    jsonPerson := `{"name": "John", "gender": 2}`

    json.Unmarshal([]byte(jsonPerson), &person)
    fmt.Printf("%+v", person)
}


Solution

  • In your code, I want to point out 2 things (which is already mention by @mkopriva)

    1. Person's fields are unexported and are not visible to unmarshal methods. You can use go vet command it will help you to prevent these kinds of errors

    2. You have implemented the Stringer interface which converts Gender value from enum to string. if you don't want to represent this type as a string you can comment String method

      func (g Gender) String() string { return [...]string{"Undefined", "Male", "Female", "Non-binary"}[g] }

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