You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:01:46