Golang解析YAML时保留浮点数精度(map[string]interface{}场景)
保留YAML浮点数精度(map[string]interface{}场景)
我用gopkg.in/yaml.v2解析YAML字符串到map[string]interface{}时遇到问题:
原代码:
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) }
实际输出丢失了浮点数的原始精度:
map[common:map[vartest1:1.2 vartest2:1.22233]]
尝试过自定义Myfloat64类型并实现UnmarshalYAML,但因为目标结构是map[string]interface{},yaml库默认将浮点数解析为float64,自定义方法不会触发。要求必须保持map[string]interface{}结构,不能用固定结构体,也不能修改输入把浮点数改为字符串。
解决方案:通过yaml.Node保留原始浮点数字符串
利用yaml.Node结构保留每个节点的原始输入内容,递归遍历节点并替换浮点数为自定义类型,同时维持map[string]interface{}结构:
package main import ( "fmt" "gopkg.in/yaml.v2" ) // PreciseFloat 同时保存浮点数的数值和原始字符串 type PreciseFloat struct { Value float64 Raw string } // String 实现Stringer接口,打印时输出原始精度 func (pf PreciseFloat) String() string { return pf.Raw } // parseNode 递归解析yaml.Node,将浮点数替换为PreciseFloat func parseNode(node *yaml.Node) (interface{}, error) { switch node.Kind { case yaml.MappingNode: m := make(map[string]interface{}) for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] valNode, err := parseNode(node.Content[i+1]) if err != nil { return nil, err } m[keyNode.Value] = valNode } return m, nil case yaml.ScalarNode: // 尝试解析为浮点数 var f float64 if err := node.Decode(&f); err == nil { return PreciseFloat{ Value: f, Raw: node.Value, }, nil } // 非浮点数类型直接解析为原始值 var v interface{} if err := node.Decode(&v); err != nil { return nil, err } return v, nil default: // 处理序列等其他类型,直接解析为原始值 var v interface{} if err := node.Decode(&v); err != nil { return nil, err } return v, nil } } func main() { kkk := "common:\n vartest1: 1.2000\n vartest2: 1.22233" var root yaml.Node if err := yaml.Unmarshal([]byte(kkk), &root); err != nil { panic(err) } result, err := parseNode(&root) if err != nil { panic(err) } // 打印带原始精度的结果 fmt.Println("原始精度输出:") printMap(result.(map[string]interface{}), "") // 如果需要使用数值进行计算 commonMap := result.(map[string]interface{})["common"].(map[string]interface{}) vartest1 := commonMap["vartest1"].(PreciseFloat).Value fmt.Printf("\nvartest1数值计算:%.4f + 0.1 = %.4f\n", vartest1, vartest1+0.1) } // printMap 格式化打印map内容 func printMap(m map[string]interface{}, prefix string) { for k, v := range m { switch val := v.(type) { case map[string]interface{}: fmt.Printf("%s%s:\n", prefix, k) printMap(val, prefix+" ") case PreciseFloat: fmt.Printf("%s%s: %s\n", prefix, k, val.Raw) default: fmt.Printf("%s%s: %v\n", prefix, k, val) } } }
运行输出:
原始精度输出: common: vartest1: 1.2000 vartest2: 1.22233 vartest1数值计算:1.2000 + 0.1 = 1.3000
原理说明
yaml.Node会保留YAML解析过程中每个节点的原始输入字符串,不会直接转换为float64丢失精度- 递归遍历节点时,识别浮点数类型的标量,用
PreciseFloat类型保存原始字符串和数值,兼顾精度保留和数值计算需求 - 最终输出的结构仍是
map[string]interface{},符合需求
内容的提问来源于stack exchange,提问作者JACK M
相关产品推荐
相关产品推荐

