Yaml: Default Value

Created on 21 Mar 2016  ·  13Comments  ·  Source: go-yaml/yaml

Hello,

I wanted to ask if it's possible to redefine the zero values for int. For example I need something like this:

...
  # int32
  max: 2147483647
  min: -2147483648

If the value is set it should use the specified value and if there is no value specified it should use the defined zero/default value. Sadly I can't use the 0 as zero value because it is a valid value for min/max.

My current solution is to use a string instead of int.

Most helpful comment

You need to implement UnmarshalYAML for Stuff:

func (s *Stuff) UnmarshalYAML(unmarshal func(interface{}) error) error {
    type rawStuff Stuff
    raw := rawStuff{} // Put your defaults here
    if err := unmarshal(&raw); err != nil {
        return err
    }

    *s = Stuff(raw)
    return nil
}

All 13 comments

You can easily set default values if you provide a non-zero value:

type Stuff struct {
  Max int32
  Min int32
}

func Parse(in []byte) error {
  var stuff = Stuff{Max: 2147483647, Min: -147483648}
  if err := yaml.Unmarshal(in, &stuff); err != nil {
    return err
  }
  ...
  return nil
}

Another option would be tu use:

type Stuff struct {
  Max *int32
  Min *int32
}

You can then detect that the value has not been set and correct it.

Is the first option actually supported? I've tested it and the property's value resets to its type's default if the value is not provided in the yaml.

Edit: I've also searched the tests and unable to find one for this case.

@vincentbernat Great tips, I was wondering about both these approaches, and they work great! Small typo, I think you need an = sign:

  var stuff = Stuff{Max: 2147483647, Min: -147483648} # type is infered

Thanks again!

How do you do this for more complex structs where the sub structs should have a default... Eg:

type SomeStruct struct {
    A     string `yaml:"a"`
    Things struct {
        Foo []*Stuff `yaml:"foo"`
    } `yaml:"things"`
    Comment string `yaml:"comment"`
}

And if I want the elements in the Foo array to have defaults...

... It seems it looks like I need to implement the unmarshaler interface... https://godoc.org/gopkg.in/yaml.v2#Unmarshaler and do that per struct... Working on that...

You need to implement UnmarshalYAML for Stuff:

func (s *Stuff) UnmarshalYAML(unmarshal func(interface{}) error) error {
    type rawStuff Stuff
    raw := rawStuff{} // Put your defaults here
    if err := unmarshal(&raw); err != nil {
        return err
    }

    *s = Stuff(raw)
    return nil
}

@vincentbernat Cool...

So it seems this:

type rawStuff Stuff

is basically an indirection to "cheat" and as a result, avoid the infinite recursion I was seeing when I didn't substitute in the temporary "raw" type... Is that analysis correct?

Is this the idiomatic pattern for this sort of thing?

Thanks again!

PS: This is now two projects that I'm using where I've seen your name pop up. I suppose I owe you a beverage at this point!

Yes, your analysis is correct. I can't say about the idiomatic part as I am just a regular user (and not a very experienced one yet). I'll take the beverage at any point, but I am likely to forget in two weeks. :)

Nice exchange. Closing as it seems the questions have been sorted.

@niemeyer I'd say this needs a doc patch before you close. Can you reopen and change the title please?

If you have a clear idea of what to document, please go ahead and submit a proposal.

You need to implement UnmarshalYAML for Stuff:

func (s *Stuff) UnmarshalYAML(unmarshal func(interface{}) error) error {
    type rawStuff Stuff
    raw := rawStuff{} // Put your defaults here
    if err := unmarshal(&raw); err != nil {
        return err
    }

    *s = Stuff(raw)
    return nil
}

Nice and smart trick, but i won't work if you pass an empty string to the unmarshaller:

func main() {
    unmarshalled := &speedProviderConfig{}
    yamlStr := ""
    err := yaml.Unmarshal([]byte(yamlStr), unmarshalled)
}

type speedProviderConfig struct {
    MinimumBytesPerSeconds int64 `yaml:"min"`
    MaximumBytesPerSeconds int64 `yaml:"max"`
}

func (c *speedProviderConfig) UnmarshalYAML(value *yaml.Node) error {
    type rawSpeedProviderConfig speedProviderConfig // create a subtype to trick goyaml. If we where using Decode() on a speedProviderConfig it will go into an infinite recursion
    conf := rawSpeedProviderConfig{
        MinimumBytesPerSeconds: 5000,
        MaximumBytesPerSeconds: 15000,
    }

    err := value.Decode(&conf)
    if err != nil {
        return errors.Wrapf(err, "bandwidth.speedProviderConfig: failed to unmarshall")
    }

    *c = speedProviderConfig(conf)

    return nil
}

The above main() function will produce a speedProvider value 0 and 0. If the strign is empty is seems that the umnarshalling is not taking place at all, and it also means that our default values wont be set.

What i recommend is :

func main() {
    unmarshalled := defaultSpeedProviderConfig()
    yamlStr := ""
    err := yaml.Unmarshal([]byte(yamlStr), unmarshalled)
}

type speedProviderConfig struct {
    MinimumBytesPerSeconds int64 `yaml:"min"`
    MaximumBytesPerSeconds int64 `yaml:"max"`
}

func defaultSpeedProviderConfig() *speedProviderConfig {
    return &speedProviderConfig{
        MinimumBytesPerSeconds: 5000,
        MaximumBytesPerSeconds: 15000,
    }
}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ldesplat picture ldesplat  ·  6Comments

lgo picture lgo  ·  5Comments

mro picture mro  ·  13Comments

thallgren picture thallgren  ·  8Comments

moul picture moul  ·  5Comments