Go – 自定义解组器 – 字符串到浮点数

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

Go - Custom unmarshaler - string to float

问题

我消费一个第三方API,它以字符串形式给出价格,有三种类型:

//输入
[
	{"id": "A", "price": "123"},
	{"id": "B", "price": "123.5"},
	{"id": "C", "price": ""}
]

//期望输出

[
	A 123,
	B 123.5,
	C 0
]

我知道我可以在我的结构体中添加,string部分,但这不能处理订单C。我在这个 GitHub问题中找到了一个关于int的问题。在那里有一个playground代码,我稍微修改了一下以适应我的需求。

如果我将其更改为float64而不是int,那么现在订单A的日志将为0。

json: cannot unmarshal string into Go struct field Order.price of type float64
[{A 0}]

我认为代码中的if string(data) == '""' {部分将检查空字符串(订单C的情况),如果是空字符串,则设置为0。但是"123"不等于""。我理解自定义解组器的其余部分,除了下面的部分。有人可以指点我正确的方向吗?

p := (*float64)(foe)
*p = f

完整代码也在这里

package main

import (
	"encoding/json"
	"fmt"
	"strings"
)

type StringToFloat float64

type Order struct {
	Id    string        `json:"id"`
	Price StringToFloat `json:"price"`
}

func (foe *StringToFloat) UnmarshalJSON(data []byte) error {
	if string(data) == `""` {
		if foe != nil {
			*foe = 0
		}
		return nil
	}

	var f float64
	err := json.Unmarshal(data, &f)
	if err != nil {
		return err
	}
	p := (*float64)(foe)
	*p = f
	return nil
}

func main() {
	jsonString := `[
		{"id": "A", "price": "123"},
		{"id": "B", "price": "123.5"},
		{"id": "C", "price": ""}
	]`
	var orders []Order
	if err := json.NewDecoder(strings.NewReader(jsonString)).Decode(&orders); err != nil {
		fmt.Println(err)
	}

	fmt.Println(orders)
}

编辑:评论@mkopriva

我调整了解组器:

func (foe *StringToFloat) UnmarshalJSON(data []byte) {
	fmt.Println(string(data))
	if string(data) == `""` {
		if foe != nil {
			*foe = 0
		}
	}

	n, err := strconv.ParseFloat(string(data), 64)
	if err != nil {
		fmt.Println(err)
	}

	*foe = StringToFloat(n)
}

这给我输出:

json: cannot unmarshal string into Go struct field Order.price of type main.StringToFloat
[{A 0} {B 0} {C 0}]
```

<details>
<summary>英文:</summary>

I consume a 3th party API that gives me the price as a string, there are 3 flavours:

//Input
[
{"id": "A", "price": "123"},
{"id": "B", "price": "123.5"},
{"id": "C", "price": ""}
]

//Expected output

[
A 123,
B 123.5,
C 0
]


I know i can add the `,string` part inside my struct but this will not handle order C. I have found [this](https://github.com/golang/go/issues/26800) github issue where this question is asked about int&#39;s. In there there is a [playground](https://go.dev/play/p/nWl2toE2RAE) code that i altered a little bit to my needs.

If i change this to float64 instead of int i get an error with the log of order A that is now 0. 

json: cannot unmarshal string into Go struct field Order.price of type float64
[{A 0}]


I thought that this `if string(data) ==  &#39;&quot;&quot;&#39; {` part of the code will check for the empty string (case order C) if so: set to 0. But `&quot;123&quot;` is not `&quot;&quot;`. The rest of the custom unmarshaler i understand apart from the part below. Can someone point me to the right direction?

p := (*float64)(foe)
*p = f



**The full code** [Also here](https://go.dev/play/p/-_BArOztsOj)

package main

import (
"encoding/json"
"fmt"
"strings"
)

type StringToFloat float64

type Order struct {
Id string json:&quot;id&quot;
Price StringToFloat json:&quot;price&quot;
}

func (foe *StringToFloat) UnmarshalJSON(data []byte) error {
if string(data) == &quot;&quot; {
if foe != nil {
*foe = 0
}
return nil
}

var f float64
err := json.Unmarshal(data, &amp;f)
if err != nil {
	return err
}
p := (*float64)(foe)
*p = f
return nil

}

func main() {
jsonString := [
{&quot;id&quot;: &quot;A&quot;, &quot;price&quot;: &quot;123&quot;},
{&quot;id&quot;: &quot;B&quot;, &quot;price&quot;: &quot;123.5&quot;},
{&quot;id&quot;: &quot;C&quot;, &quot;price&quot;: &quot;&quot;}
]

var orders []Order
if err := json.NewDecoder(strings.NewReader(jsonString)).Decode(&orders); err != nil {
fmt.Println(err)
}

fmt.Println(orders)

}


EDIT: comment @mkopriva

I adjusted the unmarshaler:

func (foe *StringToFloat) UnmarshalJSON(data []byte) {
fmt.Println(string(data))
if string(data) == &quot;&quot; {
if foe != nil {
*foe = 0
}
}

n, err := strconv.ParseFloat(string(data), 64)
if err != nil {
	fmt.Println(err)
}

*foe = StringToFloat(n)

}


What gives me this output:

json: cannot unmarshal string into Go struct field Order.price of type main.StringToFloat
[{A 0} {B 0} {C 0}]


</details>


# 答案1
**得分**: 1

请稍等,我会为您翻译这段代码。

<details>
<summary>英文:</summary>

Try this

```go
package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;strconv&quot;
	&quot;strings&quot;
)

type StringToFloat float64

type Order struct {
	Id    string        `json:&quot;id&quot;`
	Price StringToFloat `json:&quot;price&quot;`
}

func (foe *StringToFloat) UnmarshalJSON(data []byte) error {
	if string(data) == &quot;\&quot;\&quot;&quot; {
		if foe != nil {
			*foe = 0
		}
		return nil
	}
	num := strings.ReplaceAll(string(data), &quot;\&quot;&quot;, &quot;&quot;)
	n, err := strconv.ParseFloat(num, 64)
	if err != nil {
		return err
	}
	*foe = StringToFloat(n)
	return nil
}

func main() {
	jsonString := `[
        {&quot;id&quot;: &quot;A&quot;, &quot;price&quot;: &quot;123&quot;},
        {&quot;id&quot;: &quot;B&quot;, &quot;price&quot;: &quot;123.5&quot;},
        {&quot;id&quot;: &quot;C&quot;, &quot;price&quot;: &quot;&quot;}
    ]`
	var orders []Order
	if err := json.NewDecoder(strings.NewReader(jsonString)).Decode(&amp;orders); err != nil {
		fmt.Println(err)
	}

	fmt.Println(orders)
}
```

Go playground: https://go.dev/play/p/cHjNQ447eX9

Things to note:
- The function signature of UmarshalJSON func needs to be exactly the same as given to be picked up by Decode. In your updated code `error` return type was missing
- `string(data)` gives `&quot;123&quot;` so the quotes need to be removed before `ParseFloat`
- `return nil` is required after string = `&quot;&quot;` check so it dosent go further

</details>



huangapple
  • 本文由 发表于 2022年9月2日 02:00:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/73573695.html
匿名

发表评论

匿名网友

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

确定