interface vazia

Dicas de como usar interface vazia e seus riscos

Interface vazia é um tipo que aceita qualquer coisa, você pode passar o que quiser como parâmetros de função ou variáveis do tipo interface{}.

Parece muito pratico a primeira vista mas quando usamos interface{} estamos jogando pela janela a validação de tipos feita em tempo de compilação e perdemos uma das grandes vantagens de uma linguagem compilada de tipagem forte e estática.

E como a checagem de tipo não vai acontecer em tempo de compilação é sua responsabilidade checar se esta recebendo o tipo certo em tempo de execução.

Identificando o tipo

Quando queremos saber o tipo de um determinado principalmente quando estamos depurando e tentando ver se o tipo que chegou foi o topo esperado podemos usar a função fmt.Printf passando o formato %T como no exemplo.

var value interface{}
value = 1
fmt.Printf("tipo de value: %T\n", value)

No nosso exemplo declaramos a variável value como interface{} e agora podemos passar qualquer valor para ela, como passamos um inteiro a função Printf com o formato %T vai informar que a variável é do tipo int.

Vamos ver um exemplo completo

package main

import (
	"fmt"
)

func main() {
	var value interface{}
	value = 1
	fmt.Printf("tipo de value: %T\n", value)
	value = 3.14
	fmt.Printf("tipo de value: %T\n", value)
	value = "isso é uma string"
	fmt.Printf("tipo de value: %T\n", value)
}

Esse pequeno programa deve mostrar o seguinte resultado

tipo de value: int
tipo de value: float64
tipo de value: string

Switch

Inspecionar o tipo de uma variável como vimos é bem simples, ajuda bastante a depurar o código e saber se estamos recebendo o que esperamos, agora vamos fazer nosso código fazer esse trabalho sozinho.

Vamos usar a combinação do switch com o cast (type) que vai retornar o tipo da variável, veja o exemplo.

package main

import (
	"fmt"
)

func main() {
	var value interface{}
	value = 1
	switch value.(type) {
	case int:
		fmt.Println("Value é do tipo int")
	case string:
		fmt.Println("Value é do tipo string")
	default:
		fmt.Printf("Tipo %T não implementado\n", value)

	}
}

Se a variável value conter um int nosso programa vai retornar Value é do tipo int, se passarmos uma string para value o retorno vai ser Value é do tipo string e se passarmos qualquer outro valor o programa vai avisar que o suporte para aquele tipo ainda não esta implementado.

Uma vez que já sabemos o tipo da variável podemos fazer cast para o tipo correto e dai e dai usar sem problemas.

switch value.(type) {
case int:
	fmt.Println(value.(int))
case string:
	fmt.Println(value.(string))

Se não fizermos essa validação e simplesmente usar o cast para tentar converter a variável para o tipo que queremos corremos o risco do programa quebrar com um panic caso o tipo passado não seja o que estamos esperando.

map[string]interface{}

Um uso muito comum é usar interfaces vazias em conjunto com mapas para converter strings JSON para estruturas que não conhecemos com certeza o formato. Como não sabemos quais campos e nem os tipos dos campos a estrutura contem podemos usar um map[string]interface{} que basicamente casa com qualquer estrutura.

package main

import (
	"fmt"
	"encoding/json"
)

func main() {
	b := []byte(`{"Name":"Cesar","Value":10}`)
	
	var m map[string]interface{}
	m = make(map[string]interface{})
	
	err := json.Unmarshal(b, &m)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%#v\n", m)
}

Neste exemplo primeiro declaramos um array de bytes com uma estrutura JSON, depois declaramos um mapa de strings e interfaces vazias, e instanciamos na memória. Daí usamos json.Unmarshal para ler os array de bytes e popular o mapa com os campos e valores encontrados. Verificamos se houve algum erro porque sempre tem a possibilidade do JSON ser inválido e então usamos mais um pequeno truque para mostrar a estrutura. O formato %#v na função fmt.Printf para mostrar mais dados e não apenas o valor como aconteceria se usássemos apenas o formato %v.

Vamos ver um exemplo mais completo usando range para percorrer os campos do JSON e switch para desviar o código para o tipo correto.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	b := []byte(`{"Name":"Banana","Value":2.10}`)

	var m map[string]interface{}
	m = make(map[string]interface{})

	err := json.Unmarshal(b, &m)
	if err != nil {
		fmt.Println(err)
		return
	}
	for k, v := range m {
		switch v.(type) {
		case float64:
			fmt.Printf("%v %v\n", k, v.(float64))
		case string:
			fmt.Printf("%v %v\n", k, v.(string))
		default:
			fmt.Printf("Tipo %T não implementado\n", v)

		}
	}
}

Mais uma forma de como validar se a interface vazia tem o tipo que você quer é fazendo como no exemplo abaixo.

package main

import "fmt"

func main() {
	var value interface{}
	value = 1

	str, ok := value.(string)
	if !ok {
		fmt.Println("Value não é string")
	}
	fmt.Println(str)
}

Aqui usamos str, ok := value.(string) de forma que se value for do tipo string ok será true e str contera o valor propriamente dito já como string, é uma forma simples de testar o tipo sem escrever o switch case.

Espero ter esclarecido mais um pouco sobre interface vazia, seus usos e porque pode ser arriscado.

| | | | | |