DEV Community

renanbastos93
renanbastos93

Posted on

Criando um JSON Unmarshal personalizado em Go

Neste breve tutorial, vamos explorar como criar um método UnmarshalJSON personalizado em Go, adaptando-o para uma situação em que temos um campo específico na estrutura que pode ser representado como número, booleano ou string. Isto nos permitirá lidar com dados JSON de forma flexível e personalizada.


Vamos prosseguir com um exemplo prático usando a estrutura "King" com os campos "Active" e "Name". Vamos começar a criar o método UnmarshalJSON personalizado para lidar com o campo "Active" que pode ser representado como número, booleano ou string.

type King struct {
    Name   string `json:"name"`
    Active bool   `json:"active,string"`
}
//Após criar a estrutura vamos implementar a função para o UnmarshalJSON personalizado.

func (k *King) UnmarshalJSON(data []byte) (err error) {
    // Criamos um mapa dinâmico para armazenar os campos JSON.
    var fields map[string]interface{}

    // Realizamos a decodificação JSON padrão no mapa "fields".
    err = json.Unmarshal(data, &fields)
    if err != nil {
        return err
    }

    // Copiamos o valor do campo "name" do mapa para a estrutura "King".
    k.Name = fields["name"].(string)

    // Convertendo o valor do campo "active" para booleano.
    // Usamos `fmt.Sprintf` para garantir que o valor seja uma string antes da conversão.
    activeStr := fmt.Sprintf("%v", fields["active"])

    // Aqui estamos convertendo para boleano e ignoramos o erro pois se vier algo diferente do esperado sempre será falso
    k.Active, _ = strconv.ParseBool(activeStr)

    return nil
}
Enter fullscreen mode Exit fullscreen mode

Agora que entendemos como criar um Unmarshal JSON personalizado, vamos aplicar isso na função principal, chamada 'main', do pacote principal. A seguir, apresentamos o código com os resultados esperados.

func main() {
    var k King
    if err := json.Unmarshal([]byte(`{"active": 1, "name":"renan"}`), &k); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", k)
    // Output: {Name:renan Active:true}
}
Enter fullscreen mode Exit fullscreen mode

Para concluir, demonstraremos um teste unitário usando nosso método personalizado, abrangendo todos os casos para uma compreensão abrangente.


func TestKing_UnmarshalJSON(t *testing.T) {
    tests := []struct {
        name    string
        king    King
        data    []byte
        wantErr bool
    }{
        {
            name:    "Employing the number 1 to denote a true value.",
            king:    King{Name: "renan", Active: true},
            data:    []byte(`{"active": 1, "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Employing the number 0 to denote a false value.",
            king:    King{Name: "renan", Active: false},
            data:    []byte(`{"active": 0, "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the string '1' to represent the boolean value 'true'.",
            king:    King{Name: "renan", Active: true},
            data:    []byte(`{"active": "1", "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the string '0' to represent the boolean value 'false'.",
            king:    King{Name: "renan", Active: false},
            data:    []byte(`{"active": "0", "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the string 'true' to represent the boolean value 'true'",
            king:    King{Name: "renan", Active: true},
            data:    []byte(`{"active": "true", "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the bool true to represent the boolean value 'true'",
            king:    King{Name: "renan", Active: true},
            data:    []byte(`{"active": true, "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the bool false to represent the boolean value 'false'",
            king:    King{Name: "renan", Active: false},
            data:    []byte(`{"active": false, "name":"renan"}`),
            wantErr: false,
        },
        {
            name:    "Utilizing the string 'false' to represent the boolean value 'false'",
            king:    King{Name: "renan", Active: false},
            data:    []byte(`{"active": "false", "name":"renan"}`),
            wantErr: false,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var k King
            err := json.Unmarshal(tt.data, &k)
            if (err != nil) != tt.wantErr {
                t.Errorf("King.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
            }
            if k.Active != tt.king.Active || k.Name != tt.king.Name {
                t.Errorf("unmarshal failed: k[%+v]; tt.kink[%+v];", k, tt.king)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Agradeço por acompanhar este tutorial. Se você tiver alguma dúvida, comentário ou sugestão, por favor, não hesite em entrar em contato comigo. Estou à disposição para ajudar e melhorar.

Top comments (0)