假设我在服务器上有以下Go结构
type account struct {
Name string
Balance int
}
我想在传入的请求上调用json.Decode将其解析为一个帐户。
var ac account
err := json.NewDecoder(r.Body).Decode(&ac)
如果客户端发送以下请求:
{
"name": "test@example.com",
"balance": "3"
}
Decode()将返回以下错误:
json: cannot unmarshal string into Go value of type int
现在可能将其解析为“你为Balance发送了一个字符串,但你真的应该发送一个整数”,但这很棘手,因为你不知道字段名称。如果请求中有很多字段,那么它也会变得更加棘手 - 您不知道哪个字段无法解析。
在Go中获取传入请求的最佳方法是什么,并为请求中的任意数量的整数字段返回错误消息“Balance必须是字符串”?
答案 0 :(得分:3)
您可以在“余额”字段中使用自定义类型和自定义解组algorythm。
现在有两种可能性:
处理这两种类型:
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type Int int
type account struct {
Name string
Balance Int
}
func (i *Int) UnmarshalJSON(b []byte) (err error) {
var s string
err = json.Unmarshal(b, &s)
if err == nil {
var n int
n, err = strconv.Atoi(s)
if err != nil {
return
}
*i = Int(n)
return
}
var n int
err = json.Unmarshal(b, &n)
if err == nil {
*i = Int(n)
}
return
}
func main() {
for _, in := range [...]string{
`{"Name": "foo", "Balance": 42}`,
`{"Name": "foo", "Balance": "111"}`,
} {
var a account
err := json.Unmarshal([]byte(in), &a)
if err != nil {
fmt.Printf("Error decoding JSON: %v\n", err)
} else {
fmt.Printf("Decoded OK: %v\n", a)
}
}
}
只处理一种数字类型,并使用合理的错误使其他任何内容失败:
package main
import (
"encoding/json"
"fmt"
)
type Int int
type account struct {
Name string
Balance Int
}
type FormatError struct {
Want string
Got string
Offset int64
}
func (fe *FormatError) Error() string {
return fmt.Sprintf("Invalid data format at %d: want: %s, got: %s",
fe.Offset, fe.Want, fe.Got)
}
func (i *Int) UnmarshalJSON(b []byte) (err error) {
var n int
err = json.Unmarshal(b, &n)
if err == nil {
*i = Int(n)
return
}
if ute, ok := err.(*json.UnmarshalTypeError); ok {
err = &FormatError{
Want: "number",
Got: ute.Value,
Offset: ute.Offset,
}
}
return
}
func main() {
for _, in := range [...]string{
`{"Name": "foo", "Balance": 42}`,
`{"Name": "foo", "Balance": "111"}`,
} {
var a account
err := json.Unmarshal([]byte(in), &a)
if err != nil {
fmt.Printf("Error decoding JSON: %#v\n", err)
fmt.Printf("Error decoding JSON: %v\n", err)
} else {
fmt.Printf("Decoded OK: %v\n", a)
}
}
}
还有第三种可能性:为整个account
类型编写自定义解组,但它需要更多涉及的代码,因为您需要使用以下方法实际迭代输入JSON数据。
encoding/json.Decoder
类型。
阅读
之后在Go中获取传入请求的最佳方法是什么,并在请求中为任意数量的整数字段返回错误消息“Balance必须是字符串”?
更加谨慎,我承认除了你对3rd-party package implementing a parser supporting validation via JSON schema感到满意之外,为整个类型设置一个自定义解析器是唯一明智的可能性(我想我首先看this为{{1}是一个非常成熟的产品。)
答案 1 :(得分:2)
对此的解决方案可以是使用map来将JSON数据解组为:
type account struct {
Name string
Balance int
}
var str = `{
"name": "test@example.com",
"balance": "3"
}`
func main() {
var testing = map[string]interface{}{}
err := json.Unmarshal([]byte(str), &testing)
if err != nil {
fmt.Println(err)
}
val, ok := testing["balance"]
if !ok {
fmt.Println("missing field balance")
return
}
nv, ok := val.(float64)
if !ok {
fmt.Println("balance should be a number")
return
}
fmt.Printf("%+v\n", nv)
}
请参阅http://play.golang.org/p/iV7Qa1RrQZ
这里的类型断言是使用float64
完成的,因为它是Go的JSON解码器支持的默认数字类型。
应该注意的是interface{}
的使用可能不值得。
UnmarshalTypeError
(https://golang.org/pkg/encoding/json/#UnmarshalFieldError)包含Offset
字段,可以检索触发错误的JSON数据的内容。
例如,您可以返回以下类型的消息:
cannot unmarshal string into Go value of type int near `"balance": "3"`
答案 2 :(得分:0)
似乎here提供了一个实现来解决Go中的这个问题。
type account struct {
Name string
Balance int `json:",string"`
}
在我的估计中,更正确和可持续的方法是您在JavaScript之类的东西中创建客户端库并将其发布到NPM注册表中以供其他人使用(私有存储库将以相同的方式工作)。通过提供此库,您可以以有意义的方式为消费者定制API,并防止错误蔓延到主程序中。