package config
import (
"encoding/json"
"fmt"
"time"
)
type Config struct {
ListenAddr string `json:"listen_addr"`
MaxConns int `json:"max_conns"`
ReadTimeout Duration `json:"read_timeout"`
Upstreams []string `json:"upstreams"`
TLSEnabled bool `json:"tls_enabled"`
}
type Duration time.Duration
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("duration must be a string: %w", err)
}
parsed, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("invalid duration %q: %w", s, err)
}
*d = Duration(parsed)
return nil
}
func (c *Config) Validate() error {
if c.ListenAddr == "" {
return fmt.Errorf("listen_addr is required")
}
if c.MaxConns <= 0 {
return fmt.Errorf("max_conns must be positive, got %d", c.MaxConns)
}
return nil
}
package config
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
)
func LoadConfig(path string) (*Config, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open config %s: %w", path, err)
}
defer f.Close()
return Load(f, path)
}
func Load(r io.Reader, name string) (*Config, error) {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
var cfg Config
if err := dec.Decode(&cfg); err != nil {
return nil, fmt.Errorf("decode config %s: %w", name, err)
}
// Reject trailing content after the first JSON document.
if _, err := dec.Token(); !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("config %s: unexpected trailing data", name)
}
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("invalid config %s: %w", name, err)
}
return &cfg, nil
}
package main
import (
"flag"
"fmt"
"os"
"example.com/app/config"
)
func main() {
path := flag.String("config", "config.json", "path to JSON config file")
flag.Parse()
cfg, err := config.LoadConfig(*path)
if err != nil {
fmt.Fprintln(os.Stderr, "config error:", err)
os.Exit(1)
}
fmt.Printf("listening on %s (max_conns=%d, upstreams=%d)\n",
cfg.ListenAddr, cfg.MaxConns, len(cfg.Upstreams))
}
This snippet shows how a Go service loads a JSON config file while rejecting any key that doesn't map to a known struct field. By default, encoding/json silently ignores unknown keys, which hides typos like "tiemout" and makes misconfiguration hard to notice until runtime. The Config type tab models the config with explicit json struct tags, so the decoder knows exactly which fields are legal and how durations and byte sizes are represented on the wire.
The core of the strict behavior lives in the Loader tab. Load opens the file and wraps the reader in a json.Decoder, then calls dec.DisallowUnknownFields(). That single call flips the decoder into strict mode: any field in the JSON that has no corresponding struct field produces an error such as json: unknown field "tiemout" instead of being dropped. After decoding, the code calls dec.Token() once more and expects io.EOF; this guards against a second JSON document or trailing garbage in the file, which the standard decoder would otherwise accept silently.
Because raw decode errors are terse, Load wraps them with fmt.Errorf and the %w verb so callers can still errors.Is/errors.As down to the original cause while getting a message that names the file. The Duration custom type demonstrates why struct tags alone aren't enough: JSON has no duration type, so UnmarshalJSON parses a string like "30s" via time.ParseDuration, giving human-friendly config values with validation for free.
Validation beyond shape lives in Validate, invoked right after decoding. It enforces semantic rules — a non-empty ListenAddr, a positive MaxConns — that the type system can't express, returning descriptive errors early rather than letting a zero value cause a confusing failure later.
The main tab wires it together: it reads a path flag, calls LoadConfig, and exits non-zero with the wrapped error on failure. The trade-off of strict mode is rigidity — forward-compatible configs that intentionally carry extra keys will break — so this pattern fits internal services where a typo should be a hard failure, not a config-management system that must tolerate unknown future fields.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
#[arg(short, long)]
clap for CLI argument parsing with derive macros
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
Share this code
Here's the card — post it anywhere.