keep precision when unmarshling yaml

Issue

I got this code:

package main

import (
 "fmt"

 "gopkg.in/yaml.v2"
)

func main() {
 kkk := "common:\n vartest1: 1.2000\n vartest2: 1.22233"
 tmp := map[string]interface{}{}
 yaml.Unmarshal([]byte(kkk), tmp)

 fmt.Println(tmp)

}

What I expect is the precision of float keeps the same with the string. But I got this output:

map[common:map[vartest1:1.2 vartest2:1.22233]]

I tried this:

package main

import (
 "fmt"

 "gopkg.in/yaml.v2"
)

type Myfloat64 float64

func (e *Myfloat64) UnmarshalYAML(unmarshal func(interface{}) error) error {
 var test Myfloat64
 err := unmarshal(&test)
 if err != nil {
  return err
 }

 fmt.Println(test)

 *e = test

 return nil
}

func main() {
 kkk := "common:\n vartest1: 1.2000\n vartest2: 1.22233"
 tmp := map[string]interface{}{}
 yaml.Unmarshal([]byte(kkk), tmp)

 fmt.Println(tmp)

}

But the UnmarshalYAML hasn’t been called as the type Myfloat64 is not in map[string]interface{}{}. I have to ensure map[string]interface{}{} unchanged, because I can not have a fixed struct defining that unmarshaled structure.

Is there any way to keep the precision? The input must be "common:\n vartest1: 1.2000\n vartest2: 1.22233". Updating 1.2000 to string is not allowed.

Solution

Is there any way to keep the precision?

Don’t parse the string as float, instead keep the raw value, i.e. keep the string.

Here’s an example:

  • note #1: the code will need to be extended for it to be able to handle other types.
  • note #2: this is using yaml.v3
type Any struct {
    Val any
}

func (a Any) String() string {
    return fmt.Sprint(a.Val)
}

func (a *Any) UnmarshalYAML(n *yaml.Node) error {
    switch n.Kind {
    case yaml.MappingNode:
        m := map[string]Any{}
        if err := n.Decode(&m); err != nil {
            return err
        }
        a.Val = m
    case yaml.ScalarNode:
        switch n.Tag {
        case "!!float":
            // Don't parse the raw string value,
            // use it as is if you want to retain
            // its formatting.
            a.Val = n.Value
        }
    }
    return nil
}

Try it on playground.

# output:
map[common:map[vartest1:1.2000 vartest2:1.22233]]

Answered By – mkopriva

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.