Go语言中如何处理float64无穷大/NaN后转换为JSON
解决Go中float64字段含Inf/NaN的JSON序列化问题
当结构体中的float64字段包含无穷大(Infinity)或非数值(NaN)时,使用标准库json.Marshal序列化会报错,提示+Inf类型不被支持。例如从Firestore加载的结构体:
type Something struct { Id string `firestore:"id" json:"id"` NumberA float64 `firestore:"numberA" json:"numberA"` NumberB float64 `firestore:"numberB" json:"numberB"` NumberC float64 `firestore:"numberC" json:"numberC"` // 更多float64字段... }
直接序列化这类结构体触发错误时,以下是几种符合Go语言习惯的解决方案:
方案一:自定义MarshalJSON(推荐,多字段场景)
实现json.Marshaler接口,序列化时自动处理所有float64字段的Inf/NaN值。结合反射批量处理字段,避免重复代码:
import ( "encoding/json" "math" "reflect" ) type Something struct { Id string `firestore:"id" json:"id"` NumberA float64 `firestore:"numberA" json:"numberA"` NumberB float64 `firestore:"numberB" json:"numberB"` NumberC float64 `firestore:"numberC" json:"numberC"` // 其他float64字段... } func (s Something) MarshalJSON() ([]byte, error) { val := reflect.ValueOf(s) typ := val.Type() tempMap := make(map[string]interface{}, val.NumField()) for i := 0; i < val.NumField(); i++ { fieldType := typ.Field(i) // 获取JSON标签,跳过标记为"-"的字段 jsonTag := fieldType.Tag.Get("json") if jsonTag == "" || jsonTag == "-" { continue } fieldVal := val.Field(i) // 仅处理float64类型字段 if fieldType.Type.Kind() == reflect.Float64 { f := fieldVal.Float() // 将Inf/NaN转为null,可按需替换为0或其他值 if math.IsInf(f, 0) || math.IsNaN(f) { tempMap[jsonTag] = nil continue } } tempMap[jsonTag] = fieldVal.Interface() } return json.Marshal(tempMap) }
优势:
- 符合Go接口设计规范,序列化时自动触发处理,无需手动调用预处理
- 反射遍历所有字段,适配大量float64字段的场景,无需逐个编写逻辑
- 可灵活调整Inf/NaN的替换规则(比如转为0、特定字符串等)
方案二:匿名结构体+指针(少字段场景)
如果结构体float64字段不多,可通过匿名结构体封装,将float64转为指针类型,值为Inf/NaN时返回nil(JSON序列化为null):
func (s Something) MarshalJSON() ([]byte, error) { // 定义别名避免递归调用MarshalJSON type Alias Something return json.Marshal(struct { Alias NumberA *float64 `json:"numberA"` NumberB *float64 `json:"numberB"` NumberC *float64 `json:"numberC"` }{ Alias: Alias(s), NumberA: func() *float64 { if math.IsInf(s.NumberA, 0) || math.IsNaN(s.NumberA) { return nil } return &s.NumberA }(), NumberB: func() *float64 { if math.IsInf(s.NumberB, 0) || math.IsNaN(s.NumberB) { return nil } return &s.NumberB }(), NumberC: func() *float64 { if math.IsInf(s.NumberC, 0) || math.IsNaN(s.NumberC) { return nil } return &s.NumberC }(), }) }
优势:
- 逻辑直观,无需反射,性能略高
- 每个字段处理逻辑独立,可针对不同字段设置不同替换规则
方案三:预处理结构体(不推荐)
你最初考虑的反射遍历修改字段值的方式,虽可行但不够优雅,需在序列化前手动调用预处理函数:
func processFloatFields(v interface{}) { val := reflect.ValueOf(v).Elem() typ := val.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) if typ.Field(i).Type.Kind() == reflect.Float64 { f := field.Float() if math.IsInf(f, 0) || math.IsNaN(f) { field.SetFloat(0) // 替换为0或其他值 } } } } // 使用方式 var s Something // 从Firestore加载数据... processFloatFields(&s) jsonBytes, err := json.Marshal(s)
缺点:
- 每次序列化都需手动调用预处理,容易遗漏
- 修改原结构体字段值,可能影响后续业务逻辑
内容的提问来源于stack exchange,提问作者Galty
相关产品推荐
相关产品推荐

