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

时间:2016-02-09 16:41:17

标签: json go

假设我在服务器上有以下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必须是字符串”?

3 个答案:

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

    Playground link

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

    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

还有第三种可能性:为整个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{}的使用可能不值得。

UnmarshalTypeErrorhttps://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,并防止错误蔓延到主程序中。