How do I tell the client that they need to send an integer instead of a string from the Go server?

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

type account struct { Name string Balance int } 

I want to call json.Decode for an incoming request to parse it in my account.

  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:

 json: cannot unmarshal string into Go value of type int 

Now you can parse this back to "you sent the line for" Balance ", but you really had to send an integer," but it's complicated because you don't know the name of the field. It also becomes much more complicated if you have many fields in the query - you do not know which one could not be parsed.

What is the best way to accept this incoming request in Go and return the "Balance must be a string" error message for any arbitrary number of integer fields in the request?

+6
source share
3 answers

You can use a custom type with a custom template cancellation algorithm for the Balance field.

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

    link to the playing field .

  • Handle only the numeric type and do nothing else with a reasonable 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) } } } 

    link to the playing field .

There is a third possibility: write your own unmarshaler for the whole type of account , but this requires more involved code, because you will need to actually encoding/json.Decoder over the JSON input using the encoding/json.Decoder .

After reading

What is the best way to accept this incoming request in Go and return the "Balance must be a string" error message for any arbitrary number of integer fields in the request?

more carefully, I admit that a custom parser for the whole type is the only reasonable option if you do not agree with a third-party package that implements a parser that supports validation through the JSON scheme (I think I will consider this first, since juju is a very specific product).

+3
source

The solution to this could be to use a type assertion using a map to untie the JSON data in:

 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

Type approval here is done using float64 , as this is the default number type supported by the Go JSON decoder.

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

UnmarshalTypeError ( https://golang.org/pkg/encoding/json/#UnmarshalFieldError ) contains an Offset field that may allow you to get the contents of the JSON data that caused the error.

You can, for example, return a sort message:

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

It would seem that here provides an implementation to solve this problem only in Go.

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

In my assessment, a more correct and stable approach is to create a client library in the form of JavaScript and publish it in the NPM registry for other users (a private repository will work the same way). By providing this library, you can adapt the API for consumers accordingly and prevent errors creeping in your main program.

0
source

All Articles