Turn JSON bytes into a Go struct with json.Unmarshal, and control the mapping with struct tags.
json.Unmarshal takes the bytes and a pointer to the value to fill. Passing the value itself leaves it untouched, so the & matters.
A field only gets filled if it is exported. Matching is case-insensitive when no tag is present.
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Debug bool `json:"debug"`
}
data, err := os.ReadFile("config.json")
if err != nil {
// handle error
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
// handle error
}The tag names the JSON key. omitempty drops the field on the way out when it holds its zero value, and - excludes it in both directions.
Keys in the JSON with no matching field are discarded without complaint.
type User struct {
ID int `json:"id"`
Name string `json:"full_name"`
Nickname string `json:"nickname,omitempty"`
Internal string `json:"-"`
password string // unexported, never touched by encoding/json
}Telling a missing field from a zero one
A missing key and a key set to 0 both leave an int field at zero, which hides the difference between "unset" and "set to nothing".
A pointer field stays nil when the key is absent.
type Settings struct {
Retries *int `json:"retries"`
}
var s Settings
json.Unmarshal([]byte(`{}`), &s)
// s.Retries == nil, the key was absent
json.Unmarshal([]byte(`{"retries": 0}`), &s)
// *s.Retries == 0, the key was present and zeroA typo in a config key is silently ignored by default. DisallowUnknownFields on a json.Decoder turns it into an error naming the offending key.
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
var cfg Config
if err := dec.Decode(&cfg); err != nil {
// json: unknown field "prot"
}Working program
A complete program that writes a config file, reads it back into a struct, and wraps any parse failure with the file name.
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Origins []string `json:"allowed_origins"`
}
func loadConfig(path string) (Config, error) {
var cfg Config
data, err := os.ReadFile(path)
if err != nil {
return cfg, err
}
if err := json.Unmarshal(data, &cfg); err != nil {
return cfg, fmt.Errorf("parsing %s: %w", path, err)
}
return cfg, nil
}
func main() {
os.WriteFile("config.json", []byte(`{
"host": "0.0.0.0",
"port": 8080,
"allowed_origins": ["https://howtogo.dev"]
}`), 0644)
cfg, err := loadConfig("config.json")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s:%d origins=%v\n", cfg.Host, cfg.Port, cfg.Origins)
}Run it.
$ go run main.go
0.0.0.0:8080 origins=[https://howtogo.dev]