在转换为JSON之前,如何处理浮点无穷大的问题?

huangapple go评论90阅读模式
英文:

Go How to deal with float infinity before converting to JSON

问题

我遇到了一个情况,其中我有一些可能是无穷大/NaN的float64字段,尝试将其编组为JSON会导致错误,错误信息是不支持+Inf类型。

这个结构体最初是通过另一个库(Google Firestore)进行填充的。

实际上,这个结构体要大得多,有很多其他的浮点字段。

我认为我可以使用下面的循环来使用反射找到它们,不过我想知道是否有更简洁或更符合惯例的方法。

v := reflect.ValueOf(structVar)
typeOfS := v.Type()
for i := 0; i < v.NumField(); i++ {
  if typeOfS.Field(i).Type.Kind() == reflect.Float64 && math.IsInf(v.Field(i).Interface().(float64), 1) {
    // ... 我将在这里放置一些逻辑
  }
}

我不太理解如何实现自定义编组,也许这可以是处理+Inf的一种选项?

英文:

I've come across a situation where I have some float64 fields that could be infinity/NaN and trying to marshal to JSON would result in an error regarding +Inf type isn't supported.

type Something interface {
  Id string `firestore:&quot;id&quot;`
  NumberA float64 `firestore:&quot;numberA&quot;`
  NumberB float64 `firestore:&quot;numberB&quot;`
  NumberC float64 `firestore:&quot;numberC&quot;`
}

This struct gets initially populated via another library (Google Firestore).

In reality this struct is much larger with a lot more fields that are floats.

I think I could use something like this loop below using reflect to to find them all, though I wonder if there is a cleaner way or more idiomatic approach.

v := reflect.ValueOf(structVar)
typeOfS := v.Type()
for i := 0; i&lt; v.NumField(); i++ {
  if typeOfS.Field(i).Type.Kind() == reflect.Float64 &amp;&amp; math.IsInf(v.Field(i).Interface().(float64), 1) {
    // ... some logic I&#39;ll put here
  }
}

I don't understand how to implement custom marshalling so maybe that could be an option to handle +Inf?

答案1

得分: 2

自定义处理值可以通过实现Marshaler接口的自定义类型来完成。不过,你的Something类型是有问题的。它被定义为type Something interface{},而实际上应该是type Something struct

type Something struct {
    Id      string    `firestore:"id"`
    NumberA JSONFloat `firestore:"numberA"`
    NumberB JSONFloat `firestore:"numberB"`
    NumberC JSONFloat `firestore:"numberC"`
}

type JSONFloat float64

func (j JSONFloat) MarshalJSON() ([]byte, error) {
    v := float64(j)
    if math.IsInf(j, 0) {
        // 处理无穷大,将期望的值赋给v
        // 或者使用+/-表示无穷大
        s := "+"
        if math.IsInf(v, -1) {
            s = "-"
        }
        return []byte(s), nil
    }
    return json.Marshal(v) // 将结果作为标准的float64进行编组
}

func (j *JSONFloat) UnmarshalJSON(v []byte) error {
    if s := string(v); s == "+" || s == "-" {
        // 如果+/-表示无穷大
        if s == "+" {
            *j = JSONFloat(math.Inf(1))
            return nil
        }
        *j = JSONFloat(math.Inf(-1))
        return nil
    }
    // 只是一个普通的浮点数值
    var fv float64
    if err := json.Unmarshal(v, &fv); err != nil {
        return err
    }
    *j = JSONFloat(fv)
    return nil
}

这样应该可以了。

英文:

Custom handling of values can be done through custom types that implement the Marshaler interface. Your Something type, though, is malformed. It's defined as type Something interface{}, whereas that really ought the be a type Something struct:

type Something struct {
    Id      string    `firestore:&quot;id&quot;`
    NumberA JSONFloat `firestore:&quot;numberA&quot;`
    NumberB JSONFloat `firestore:&quot;numberB&quot;`
    NumberC JSONFloat `firestore:&quot;numberC&quot;`
}

type JSONFloat float64

func (j JSONFloat) MarshalJSON() ([]byte, error) {
    v := float64(j)
    if math.IsInf(j, 0) {
        // handle infinity, assign desired value to v
        // or say +/- indicates infinity
        s := &quot;+&quot;
        if math.IsInf(v, -1) {
            s = &quot;-&quot;
        }
        return []byte(s), nil
    }
    return json.Marshal(v) // marshal result as standard float64
}

func (j *JSONFloat) UnsmarshalJSON(v []byte) error {
    if s := string(v); s == &quot;+&quot; || s == &quot;-&quot; {
        // if +/- indiciates infinity
        if s == &quot;+&quot; {
            *j = JSONFloat(math.Inf(1))
            return nil
        }
        *j = JSONFloat(math.Inf(-1))
        return nil
    }
    // just a regular float value
    var fv float64
    if err := json.Unmarshal(v, &amp;fv); err != nil {\
        return err
    }
    *j = JSONFloat(fv)
    return nil
}

That should do it

答案2

得分: 0

我创建了xhhuango/json来支持NaN、+Inf和-Inf。

type T struct {
    N  float64
    IP float64
    IN float64
}

func TestMarshalNaNAndInf(t *testing.T) {
    s := T{
        N:  math.NaN(),
        IP: math.Inf(1),
        IN: math.Inf(-1),
    }
    got, err := Marshal(s)
    if err != nil {
        t.Errorf("Marshal() error: %v", err)
    }
    want := `{"N":NaN,"IP":+Inf,"IN":-Inf}`
    if string(got) != want {
        t.Errorf("Marshal() = %s, want %s", got, want)
    }
}

func TestUnmarshalNaNAndInf(t *testing.T) {
    data := []byte(`{"N":NaN,"IP":+Inf,"IN":-Inf}`)
    var s T
    err := Unmarshal(data, &s)
    if err != nil {
        t.Fatalf("Unmarshal: %v", err)
    }
    if !math.IsNaN(s.N) || !math.IsInf(s.IP, 1) || !math.IsInf(s.IN, -1)     {
        t.Fatalf("after Unmarshal, s.N=%f, s.IP=%f, s.IN=%f, want NaN, +Inf, -Inf", s.N, s.IP, s.IN)
    }
}
英文:

I created xhhuango/json to support NaN, +Inf, and -Inf.

type T struct {
    N  float64
    IP float64
    IN float64
}

func TestMarshalNaNAndInf(t *testing.T) {
    s := T{
        N:  math.NaN(),
        IP: math.Inf(1),
        IN: math.Inf(-1),
    }
    got, err := Marshal(s)
    if err != nil {
        t.Errorf(&quot;Marshal() error: %v&quot;, err)
    }
    want := `{&quot;N&quot;:NaN,&quot;IP&quot;:+Inf,&quot;IN&quot;:-Inf}`
    if string(got) != want {
        t.Errorf(&quot;Marshal() = %s, want %s&quot;, got, want)
    }
}

func TestUnmarshalNaNAndInf(t *testing.T) {
    data := []byte(`{&quot;N&quot;:NaN,&quot;IP&quot;:+Inf,&quot;IN&quot;:-Inf}`)
    var s T
    err := Unmarshal(data, &amp;s)
    if err != nil {
        t.Fatalf(&quot;Unmarshal: %v&quot;, err)
    }
    if !math.IsNaN(s.N) || !math.IsInf(s.IP, 1) || !math.IsInf(s.IN, -1)     {
        t.Fatalf(&quot;after Unmarshal, s.N=%f, s.IP=%f, s.IN=%f, want NaN, +Inf, -Inf&quot;, s.N, s.IP, s.IN)
    }
}

huangapple
  • 本文由 发表于 2022年10月24日 17:48:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/74179310.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定