Dev (Back & Front)ARTIGO

Função recursiva em Go para acessar valores em mapas aninhados

GO | Recentemente, recebi um retorno de uma API que continha um JSON com uma estrutura complexa, grande e aninhada. Eu precisava acessar um valor

Função recursiva em Go para acessar valores em mapas aninhados
Imagem: Cesar Gimenes

GO | Recentemente, recebi um retorno de uma API que continha um JSON com uma estrutura complexa, grande e aninhada. Eu precisava acessar um valor específico dentro dessa estrutura, mas o caminho para esse valor poderia variar.

A ideia então foi criar uma função recursiva que recebesse uma lista com o caminho (path) para o valor desejado e a estrutura de dados (um mapa aninhado) e retornasse o valor correspondente, se encontrado.

Acredito que existem maneiras mais espertas de fazer isso, mas essa abordagem recursiva funcionou bem para o meu caso.

package main

import (
	"fmt"
)

func GetFromMap(parts []string, current any) (any, bool) {
	fmt.Printf("parts: %v, current: %v\n", parts, current)

	if len(parts) == 0 {
		return current, true
	}

	m, ok := current.(map[string]any)
	if !ok { // not a map, cannot descend
		return nil, false
	}

	key := parts[0]
	next, exists := m[key]
	if !exists {
		return nil, false
	}

	return GetFromMap(parts[1:], next)
}

func MustGetFromMap(path []string, data any) any {
	val, ok := GetFromMap(path, data)
	if !ok {
		panic(fmt.Sprintf("path %v not found in map", path))
	}
	return val
}

E aqui está um exemplo de como você pode testar essa função:

package main

import "testing"

func TestGetFromMap(t *testing.T) {
	data := map[string]any{
		"not_a_map": "just a string",
		"a": map[string]any{
			"b": map[string]any{
				"c": 42,
			},
			"d": "hello",
		},
		"x": 3.14,
	}

	tests := []struct {
		path          []string
		expectedVal   any
		expectedFound bool
	}{
		{
			path:          []string{"a", "b", "c"},
			expectedVal:   42,
			expectedFound: true,
		},
		{
			path:          []string{"a", "d"},
			expectedVal:   "hello",
			expectedFound: true,
		},
		{
			path:          []string{"x"},
			expectedVal:   3.14,
			expectedFound: true,
		},
		{
			path:          []string{"a", "not_found"},
			expectedVal:   nil,
			expectedFound: false,
		},
	}

	for _, tt := range tests {
		val, found := GetFromMap(tt.path, data)
		if found != tt.expectedFound {
			t.Errorf("GetFromMap(%v) found = %v; want %v", tt.path, found, tt.expectedFound)
		}
		if val != tt.expectedVal {
			t.Errorf("GetFromMap(%v) = %v; want %v", tt.path, val, tt.expectedVal)
		}
	}
}

E, por favor, quando for criar uma API, lembre-se do seu colega programador que vai consumir a API. Estruturas simples e planas que não mudam de formato com frequência são melhores. Aliás, simples sempre é melhor.

Cesar GimenesAutor

Trabalha com tecnologia desde a década de 90. Já atuou na área de educação e participou de projetos de mobilidade de grande volume para laboratórios farmacêuticos. Criou games tanto para PC, como para iOS. Hoje está direcionando seus esforços em plataformas de Sistemas Embarcados, IoT, microservices e cloud computing. É um entusiasta de tecnologias como Golang e Docker.

Ver perfil