gogo-zap

How does Go unmarshall a string into a wrapped atomc int?


Uber's logging library Zap has a Config struct, in which the log level is defined as follows:

type Config struct {
    Level AtomicLevel `json:"level" yaml:"level"`
}

Where the type AtomicLevel is a struct that wraps Go's atomic.Int32:

type AtomicLevel struct {
    l *atomic.Int32
}

With this setup, how can Go's json.Unmarshall successfully unmarshalls a string, say, "debug" into the correct atomic int value, as the following example illustrates?

package main

import (
    "encoding/json"

    "go.uber.org/zap"
)

func main() {
    // For some users, the presets offered by the NewProduction, NewDevelopment,
    // and NewExample constructors won't be appropriate. For most of those
    // users, the bundled Config struct offers the right balance of flexibility
    // and convenience. (For more complex needs, see the AdvancedConfiguration
    // example.)
    //
    // See the documentation for Config and zapcore.EncoderConfig for all the
    // available options.
    rawJSON := []byte(`{
      "level": "debug",
      "encoding": "json",
    }`)

    var cfg zap.Config
    if err := json.Unmarshal(rawJSON, &cfg); err != nil {
        panic(err)
    }
    logger, err := cfg.Build()
    if err != nil {
        panic(err)
    }
    defer logger.Sync()

    logger.Info("logger construction succeeded")
}


Solution

  • AtomicLevel has an UnmarshalText method, which means it implements the encoding.TextUnmarshaler interface. When the JSON decoder sees a JSON string and the destination is a type that implements TextUnmarshaler, its UnmarshalText method is called to convert the string into an appropriate value (unless the type implements json.Unmarshaler, in which case that takes precedence. AtomicLevel doesn't.)