如何告诉客户端他们需要从Go服务器发送整数而不是字符串?

时间:2022-08-29 09:53:32

Let's say I have the following Go struct on the server

假设我在服务器上有以下Go结构

type account struct {
    Name    string
    Balance int
}

I want to call json.Decode on the incoming request to parse it into an account.

我想在传入的请求上调用json.Decode将其解析为一个帐户。

    var ac account
    err := json.NewDecoder(r.Body).Decode(&ac)

If the client sends the following request:

如果客户端发送以下请求:

{
    "name": "test@example.com", 
    "balance": "3"
}

Decode() will return the following error:

Decode()将返回以下错误:

json: cannot unmarshal string into Go value of type int

Now it's possible to parse that back into "you sent a string for Balance, but you really should have sent an integer", but it's tricky, because you don't know the field name. It also gets a lot trickier if you have a lot of fields in the request - you don't know which one failed to parse.

现在可以将其解析为“你为Balance发送了一个字符串,但你真的应该发送一个整数”,但这很棘手,因为你不知道字段名称。如果请求中有很多字段,那么它也会变得更加棘手 - 您不知道哪个字段无法解析。

What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?

在Go中获取传入请求的最佳方法是什么,并为请求中的任意数量的整数字段返回错误消息“Balance必须是字符串”?

3 个解决方案

#1


3  

You can use a custom type with custom unmarshaling algorythm for your "Balance" field.

您可以将自定义类型与自定义解组algorythm用于“余额”字段。

Now there are two possibilities:

现在有两种可能性:

  • Handle both types:

    处理这两种类型:

    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)
            }
        }
    }
    

    Playground link.

    游乐场链接。

  • Handle only a numeric type, and fail anything else with a sensible error:

    只处理一种数字类型,并使用合理的错误使其他任何操作失败:

    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)
            }
        }
    }
    

    Playground link.

    游乐场链接。

There is a third possibility: write custom unmarshaler for the whole account type, but it requires more involved code because you'd need to actually iterate over the input JSON data using the methods of the encoding/json.Decoder type.

还有第三种可能性:为整个帐户类型编写自定义解组,但它需要更多涉及的代码,因为您需要使用encoding / json.Decoder类型的方法实际迭代输入JSON数据。

After reading your

看完你的

What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?

在Go中获取传入请求的最佳方法是什么,并为请求中的任意数量的整数字段返回错误消息“Balance必须是字符串”?

more carefully, I admit having a custom parser for the whole type is the only sensible possibility unless you are OK with a 3rd-party package implementing a parser supporting validation via JSON schema (I think I'd look at this first as juju is a quite established product).

更谨慎的是,我承认为整个类型设置一个自定义解析器是唯一合理的可能性,除非你可以通过JSON模式实现支持验证的解析器的第三方包(我想我会先看看这个,因为juju是一个非常成熟的产品)。

#2


2  

A solution for this could be to use a type assertion by using a map to unmarshal the JSON data into:

对此的解决方案可以是通过使用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)
}

See http://play.golang.org/p/iV7Qa1RrQZ

见http://play.golang.org/p/iV7Qa1RrQZ

The type assertion here is done using float64 because it is the default number type supported by Go's JSON decoder.

这里的类型断言是使用float64完成的,因为它是Go的JSON解码器支持的默认数字类型。

It should be noted that this use of interface{} is probably not worth the trouble.

应该注意的是,使用interface {}可能不值得。

The UnmarshalTypeError (https://golang.org/pkg/encoding/json/#UnmarshalFieldError) contains an Offset field that could allow retrieving the contents of the JSON data that triggered the error.

UnmarshalTypeError(https://golang.org/pkg/encoding/json/#UnmarshalFieldError)包含一个Offset字段,可以允许检索触发错误的JSON数据的内容。

You could for example return a message of the sort:

例如,您可以返回排序的消息:

cannot unmarshal string into Go value of type int near `"balance": "3"`

#3


0  

It would seem that here provides an implementation to work around this issue in Go only.

似乎这里提供了一个实现来解决Go中的这个问题。

type account struct {
    Name    string
    Balance int `json:",string"`
}

In my estimation the more correct and sustainable approach is for you to create a client library in something like JavaScript and publish it into the NPM registry for others to use (private repository would work the same way). By providing this library you can tailor the API for the consumers in a meaningful way and prevent errors creeping into your main program.

在我的估计中,更正确和可持续的方法是您在JavaScript之类的东西中创建客户端库并将其发布到NPM注册表中以供其他人使用(私有存储库将以相同的方式工作)。通过提供此库,您可以以有意义的方式为消费者定制API,并防止错误蔓延到主程序中。

#1


3  

You can use a custom type with custom unmarshaling algorythm for your "Balance" field.

您可以将自定义类型与自定义解组algorythm用于“余额”字段。

Now there are two possibilities:

现在有两种可能性:

  • Handle both types:

    处理这两种类型:

    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)
            }
        }
    }
    

    Playground link.

    游乐场链接。

  • Handle only a numeric type, and fail anything else with a sensible error:

    只处理一种数字类型,并使用合理的错误使其他任何操作失败:

    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)
            }
        }
    }
    

    Playground link.

    游乐场链接。

There is a third possibility: write custom unmarshaler for the whole account type, but it requires more involved code because you'd need to actually iterate over the input JSON data using the methods of the encoding/json.Decoder type.

还有第三种可能性:为整个帐户类型编写自定义解组,但它需要更多涉及的代码,因为您需要使用encoding / json.Decoder类型的方法实际迭代输入JSON数据。

After reading your

看完你的

What's the best way to take that incoming request, in Go, and return the error message, "Balance must be a string", for any arbitrary number of integer fields on a request?

在Go中获取传入请求的最佳方法是什么,并为请求中的任意数量的整数字段返回错误消息“Balance必须是字符串”?

more carefully, I admit having a custom parser for the whole type is the only sensible possibility unless you are OK with a 3rd-party package implementing a parser supporting validation via JSON schema (I think I'd look at this first as juju is a quite established product).

更谨慎的是,我承认为整个类型设置一个自定义解析器是唯一合理的可能性,除非你可以通过JSON模式实现支持验证的解析器的第三方包(我想我会先看看这个,因为juju是一个非常成熟的产品)。

#2


2  

A solution for this could be to use a type assertion by using a map to unmarshal the JSON data into:

对此的解决方案可以是通过使用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)
}

See http://play.golang.org/p/iV7Qa1RrQZ

见http://play.golang.org/p/iV7Qa1RrQZ

The type assertion here is done using float64 because it is the default number type supported by Go's JSON decoder.

这里的类型断言是使用float64完成的,因为它是Go的JSON解码器支持的默认数字类型。

It should be noted that this use of interface{} is probably not worth the trouble.

应该注意的是,使用interface {}可能不值得。

The UnmarshalTypeError (https://golang.org/pkg/encoding/json/#UnmarshalFieldError) contains an Offset field that could allow retrieving the contents of the JSON data that triggered the error.

UnmarshalTypeError(https://golang.org/pkg/encoding/json/#UnmarshalFieldError)包含一个Offset字段,可以允许检索触发错误的JSON数据的内容。

You could for example return a message of the sort:

例如,您可以返回排序的消息:

cannot unmarshal string into Go value of type int near `"balance": "3"`

#3


0  

It would seem that here provides an implementation to work around this issue in Go only.

似乎这里提供了一个实现来解决Go中的这个问题。

type account struct {
    Name    string
    Balance int `json:",string"`
}

In my estimation the more correct and sustainable approach is for you to create a client library in something like JavaScript and publish it into the NPM registry for others to use (private repository would work the same way). By providing this library you can tailor the API for the consumers in a meaningful way and prevent errors creeping into your main program.

在我的估计中,更正确和可持续的方法是您在JavaScript之类的东西中创建客户端库并将其发布到NPM注册表中以供其他人使用(私有存储库将以相同的方式工作)。通过提供此库,您可以以有意义的方式为消费者定制API,并防止错误蔓延到主程序中。