I'm not sure what is causing this, but the reason it throws it is when I call dec.DisallowUnknownFields() it somehow thinks that password isn't a field in the User struct/request body. Albeit, the password JSON is "-". So I thought originally to change the JSON to "password" for struct field Password but that throws an
internal server error: illegal base64 data at input byte 4
Request Body in Postman
{
"firstname": "George",
"lastname": "Costanza",
"email": "george#gmail.com",
"phone": "703-123-4567",
"password": "abc12"
}
I also tried to change the type of password to string, which also didn't work but arguably would be the wrong solution because we should store passwords as hashes in the DB. So, at this point I've run out of places to turn to as to why this is happening...
models.go
type User struct {
ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
Email string `json:"email"`
Phone string `json:"phone"`
Password []byte `json:"-"`
Created time.Time `json:"-"`
Active bool `json:"-"`
Address Address `json:"address,omitempty"`
}
Helpers.go
type malformedRequest struct {
status int
msg string
}
func (mr *malformedRequest) Error() string {
return mr.msg
}
func (app *appInjection) decodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error {
if r.Header.Get("Content-Type") != "" {
value, _ := header.ParseValueAndParams(r.Header, "Content-Type")
if value != "application/json" {
msg := "Content-Type header is not application/json"
return &malformedRequest{status: http.StatusUnsupportedMediaType, msg: msg}
}
}
r.Body = http.MaxBytesReader(w, r.Body, 1048576)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err := dec.Decode(&dst)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
switch {
case errors.As(err, &syntaxError):
msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset)
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
case errors.Is(err, io.ErrUnexpectedEOF):
msg := fmt.Sprintf("Request body contains badly-formed JSON")
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
case errors.As(err, &unmarshalTypeError):
msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset)
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
msg := fmt.Sprintf("Request body contains unknown field %s", fieldName)
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
case errors.Is(err, io.EOF):
msg := "Request body must not be empty"
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
case err.Error() == "http: request body too large":
msg := "Request body must not be larger than 1MB"
return &malformedRequest{status: http.StatusRequestEntityTooLarge, msg: msg}
default:
return err
}
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
msg := "Request body must only contain a single JSON object"
return &malformedRequest{status: http.StatusBadRequest, msg: msg}
}
return nil
}
Handlers.go
func (app *appInjection) RegisterUser(w http.ResponseWriter, r *http.Request) {
// Guide addressing headers, syntax error's, and preventing extra data fields
// https://www.alexedwards.net/blog/how-to-properly-parse-a-json-request-body
w.Header().Set("Content-Type", "application/json")
var newUser models.User
//Parse the form data
//err := json.NewDecoder(r.Body).Decode(&newUser)
err := app.decodeJSONBody(w, r, &newUser)
if err != nil {
var mr *malformedRequest
if errors.As(err, &mr) {
http.Error(w, mr.msg, mr.status)
//app.clientError(w, mr.status)
} else {
log.Println(err.Error())
//app.clientError(w, http.StatusInternalServerError)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
}
//TODO: Validate the form
//If there is no error and the form is validated, create a new user from http request
//Insert the new user into the database
uid, _ := app.user.Insert(
newUser.FirstName,
newUser.LastName,
newUser.Email,
newUser.Phone,
string(newUser.Password))
json.NewEncoder(w).Encode("Record Inserted")
json.NewEncoder(w).Encode(uid)
}
User.go
func (u *UserFunctions) Insert(firstname, lastname, email, phone, password string) (primitive.ObjectID, error) {
//Insert user to the database
userCollection := u.CLIENT.Database("queue").Collection("users")
var user models.User
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
object, _ := primitive.ObjectIDFromHex("")
return object, err
}
user.FirstName = firstname
user.LastName = lastname
user.Email = email
user.Phone = phone
user.Password = hashedPassword
user.Created = time.Now().UTC()
user.Active = true
//Insert the user into the database
result, err := userCollection.InsertOne(context.TODO(), user)
if err != nil {
fmt.Println(err)
}
//Check ID of the inserted document
insertedID := result.InsertedID.(primitive.ObjectID)
//fmt.Println(insertedID)
return insertedID, nil
}
When I run the request to sign up a user in Postman it throws the message
Request body contains unknown field "password"
As folks already linked extensively, since JSON cannot contain bytes directly, the Go Json parser expects any []byte objects to be stored in Base64 in the input data.
Your problem, therefore, is not necessarily on the Go end. Your problem is a mismatch with what you told Go to expect - []byte, Base64 encoded - and what you told the client to send - "abc12" - which is not a valid base64 value.
One simple solution for your stated case would be to send a base64 encoded version of the same string:
{
"firstname": "George",
"lastname": "Costanza",
"email": "george#gmail.com",
"phone": "703-123-4567",
"password": "YWJjMTIK"
}
But I'd like to dig just a little bit deeper.
string [...] arguably would be the wrong solution because we should store passwords as hashes in the DB
You might store passwords hashed into bytes in the database, but that's not what's coming int from curl . So to me, your real problem is that you took 2 different kinds of data - user provided password and DB hashed representation - and tried to mash them into the same data space, which has absolutely no value. After all, if you were setting a password, the current hash, if any, is irrelevant, and if you're checking the password, you'd be checking what they inputted.
This is the solution I'd prefer:
type User struct {
... ... ...
HashedPassword []byte `json:"-"`
Password string `json:"password"`
}
A simple solution would be
var newUser struct {
models.User
Password string `json:"password"`
}
//Parse the form data
//err := json.NewDecoder(r.Body).Decode(&newUser)
err := app.decodeJSONBody(w, r, &newUser)
Related
Using a post API request I want to receive a JWT token for the response.
For that, I want to send the username and password as JSON body input to the API. The username and password should be taken dynamically from the user as a JSON payload.
Currently, I am hardcoding the user name and password using strings.NewReader which is not suitable as this is a web application and many users will be using it.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/joho/godotenv"
)
var authToken string
type authorization struct {
AuthorizationToken string `json:"authorizationToken"`
ExpiresTimestamp string `json:"expiresTimestamp"`
}
func main() {
enverr := godotenv.Load(".env")
if enverr != nil {
fmt.Println("Error loading .env file")
os.Exit(1)
}
token()
}
func token() {
authorizationUrl := os.Getenv("authorizationUrl")
requestBody := strings.NewReader(`
{
"name" : "testuser",
"pwd" : "testpwd",
"hName" : "hname"
}
`)
response, err := http.Post(authorizationUrl, "application/json", requestBody)
if err != nil {
panic(err)
}
defer response.Body.Close()
content, _ := ioutil.ReadAll(response.Body)
var result authorization
if err := json.Unmarshal(content, &result); err != nil { // Parse []byte to the go struct pointer
fmt.Println("Can not unmarshal JSON")
}
authToken := result.AuthorizationToken
fmt.Println(PrettyPrint(authToken))
}
func PrettyPrint(i interface{}) string {
s, _ := json.MarshalIndent(i, "", "\t")
return string(s)
}
You can use http requests body (for POST request)
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
c := Credentials{}
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
err: = decoder.Decode(&c)
if err!=nil{
// handle it
}
// use it
fmt.Println(c.Username, c.Password)
If you are intending to distribute the application as an executable (aka command line in the Unix world), you can either:
Provide the username and password as program arguments, os.Args (omitting unchanged code):
var inputTemplate = `
{
"name" : "USER",
"pwd" : "PWD",
"hName" : "hname"
}
`
func token() {
authorizationUrl := os.Getenv("authorizationUrl")
requestBody := strings.Replace(strings.Replace(inputTemplate, "PWD", os.Args[2], -1), "USER", os.Args[1], -1)
response, err := http.Post(authorizationUrl, "application/json", requestBody)
// ...
}
You should then be able to run your program (once compiled): ./filename user password
Or as better alternative, having sensitive data involved, have the username and password input from standard input with password being echoed (hiding the characters):
import (
// ...
"syscall"
"golang.org/x/term"
)
var inputTemplate = `
{
"name" : "USER",
"pwd" : "PWD",
"hName" : "hname"
}
`
func token() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter username: ")
username := reader.ReadString('\n') // handle error
fmt.Print("Enter password: ")
pwdbytes := term.ReadPassword(int(syscall.Stdin))
password := string(pwdbytes)
authorizationUrl := os.Getenv("authorizationUrl")
requestBody := strings.Replace(strings.Replace(inputTemplate, "PWD", password, -1), "USER", username, -1)
response, err := http.Post(authorizationUrl, "application/json", requestBody)
// ...
}
some API endpoint may return successful result or error like this:
// ok
{
"status": "ok",
"payload": {
"id": 10,
"title": "Sample"
},
"request_id": "lx-VHr4OLm"
}
// error
{
"status": "error",
"payload": {
"message": "internal error"
},
"trace_id": "lx-VHr4OLm"
}
I'm trying to find elegant way to parse with Go, something like this
.... some code
if status == "ok" {
struct := AppStruct{} // AppStruct contains 2 fields: id and title
_ := json.Unmarshall(payloadBody, &struct)
return struct
} else {
errorStruct := ErrorStruct{} // contains 1 field for message.
_ := json.Unmarshall(payloadBody, &errorStruct)
return nil, errors.New(errorStruct.Message)
}
My current code not works for success payload:
var result map[string]interface{}
jsonErr := json.Unmarshal(body, &result)
if jsonErr != nil {
return nil, jsonErr
}
if result["status"] == "error" {
errorPayload := result["payload"].(map[string]string)
return nil, errors.New(errorPayload["message"])
} else if result["status"] == "ok" {
apiResponse := AppInfo{}
jsonErr := json.Unmarshal([]byte(result["payload"].(string)), &apiResponse)
if jsonErr != nil {
return nil, jsonErr
}
return &apiResponse, nil
}
And I got runtime error on line json.Unmarshal([]byte(result["payload"].(string)), &apiResponse)
http: panic serving [::1]:51091: interface conversion: interface {} is
map[string]interface {}, not string
Surely, I may have 2 structs: for success response and for error one but I assume it's too complicated way to solve my problem.
How to parse this JSON in elegant way?
I'm really not sure what the problem is. The standard encoding/json doesn't require the struct to match all fields in the JSON data. It's quite easy to handle this with a single, simple type:
type Payload struct {
ID int `json:"id"`
Title string `json:"title"`
Message string `json:"message"`
}
type Response struct {
Status string `json:"status"`
ID string `json:"request_id"`
TraceID string `json:"trace_id"`
Payload Payload `json:"payload"`
}
Then just unmarshal the response in the Response struct:
var resp Response
if err := json.Unmarshal(body, &resp); err != nil {
return err
}
Then you can simply check the Status field, and work out what to do next. For example:
if resp.Status == "error" {
return fmt.Errorf("invalid response: %s - %s", resp.TraceID, resp.Payload.Message)
}
// handle resp.Payload.ID and resp.Payload.Title fields
return nil
You could move those checks for the status field to receiver functions on the response object, depending on the complexity and your specific needs.
Perhaps it's worth using pointer fields for those fields that aren't set in the normal response, and tag them with the omitempty option:
type Payload struct {
ID int `json:"id"`
Title string `json:"title"`
Message *string `json:"message,omitempty"`
}
type Response struct {
Status string `json:"status"`
ID string `json:"request_id"`
TraceID *string `json:"trace_id,omitempty"`
Payload Payload `json:"payload"`
}
With types like this, no longer have to rely on hard-coded string constants to check for errors. Instead, you can easily implement a more generic check like so:
func (r Response) IsError() bool {
return (r.TraceID == nil) // will be set in case of an error response
}
Update
As you pointed out in the comments, the response body is actually substantially larger than the 2 fields in the example. Of course, copy-pasting the struct definitions, or writing mapping functions to map the Payload onto the type you already have is a bit pointless.
The answer here is: composition.
type Payload struct {
AppStruct // embedded the AppStruct type
Message *string `json:"message"`
}
The Response type stays as it is. If the response is successful, you can get the AppStruct directly from the response like so:
appStruct := resp.Payload.AppStruct
This works because the type is embedded. Note that there aren't any json tags there. The embedded struct, at least as far as the unmarshalling is concerned, is a part of the Payload struct. Therefore, all the exported fields in that type will be unmarshalled directly into the struct.
Thanks https://stackoverflow.com/users/965900/mkopriva for idea to use json.RawMessage
My final solution:
func parsePayload(response []byte, successPayload interface{}) error {
var result map[string]json.RawMessage
jsonErr := json.Unmarshal(response, &result)
if jsonErr != nil {
return jsonErr
}
var status string
jsonErr = json.Unmarshal(result["status"], &status)
if jsonErr != nil {
return jsonErr
}
if status == "ok" {
jsonErr = json.Unmarshal(result["payload"], &successPayload)
if jsonErr != nil {
return jsonErr
}
return nil
} else if status == "error" {
errorPayload := ErrorPayload{}
jsonErr = json.Unmarshal(result["payload"], &errorPayload)
if jsonErr != nil {
return jsonErr
}
return errors.New(errorPayload.Message)
}
log.Printf("Unknown http result status: %s", status)
return errors.New("internal error")
}
type ErrorPayload struct {
Message string `json:"message"`
}
//usage
type AppInfo struct {
Id int `json:"app_id"`
Title string `json:"app_title"`
}
body := ... // read body
appInfo := AppInfo{}
parseErr := parsePayload(body, &appInfo)
if parseErr != nil {
return nil, parseErr
}
log.Printf("Parsed app %v", appInfo)
return &appInfo, nil
Is there an easy way to check if each field of myStruct was mapped by using json.Unmarshal(jsonData, &myStruct).
The only way I could image is to define each field of a struct as pointer, otherwise you will always get back an initialized struct.
So every jsonString that is an object (even an empty one {}) will return an initialized struct and you cannot tell if the json represented your struct.
The only solution I could think of is quite uncomfortable:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name *string `json:name`
Age *int `json:age`
Male *bool `json:male`
}
func main() {
var p *Person
err := json.Unmarshal([]byte("{}"), &p)
// handle parse error
if err != nil {
return
}
// handle json did not match error
if p.Name == nil || p.Age == nil || p.Male == nil {
return
}
// now use the fields with dereferencing and hope you did not forget a nil check
fmt.Println("Hello " + *p.Name)
}
Maybe one could use a library like govalidator and use SetFieldsRequiredByDefault. But then you still have to execute the validation and still you are left with the whole pointer dereferencing for value retrieval and the risk of nil pointer.
What I would like is a function that returns my unmarshaled json as a struct or an error if the fields did not match. The only thing the golang json library offers is an option to fail on unknown fields but not to fail on missing fields.
Any idea?
Another way would be to implement your own json.Unmarshaler which uses reflection (similar to the default json unmarshaler):
There are a few points to consider:
if speed is of great importance to you then you should write a benchmark to see how big the impact of the extra reflection is. I suspect its negligible but it can't hurt to write a small go benchmark to get some numbers.
the stdlib will unmarshal all numbers in your json input into floats. So if you use reflection to set integer fields then you need to provide the corresponding conversion yourself (see TODO in example below)
the json.Decoder.DisallowUnknownFields function will not work as expected with your type. You need to implement this yourself (see example below)
if you decide to take this approach you will make your code more complex and thus harder to understand and maintain. Are you actually sure you must know if fields are omitted? Maybe you can refactor your fields to make good usage of the zero values?
Here a fully executable test of this approach:
package sandbox
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
)
type Person struct {
Name string
City string
}
func (p *Person) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
err := json.Unmarshal(data, &m)
if err != nil {
return err
}
v := reflect.ValueOf(p).Elem()
t := v.Type()
var missing []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
val, ok := m[field.Name]
delete(m, field.Name)
if !ok {
missing = append(missing, field.Name)
continue
}
switch field.Type.Kind() {
// TODO: if the field is an integer you need to transform the val from float
default:
v.Field(i).Set(reflect.ValueOf(val))
}
}
if len(missing) > 0 {
return errors.New("missing fields: " + strings.Join(missing, ", "))
}
if len(m) > 0 {
extra := make([]string, 0, len(m))
for field := range m {
extra = append(extra, field)
}
// TODO: consider sorting the output to get deterministic errors:
// sort.Strings(extra)
return errors.New("unknown fields: " + strings.Join(extra, ", "))
}
return nil
}
func TestJSONDecoder(t *testing.T) {
cases := map[string]struct {
in string
err string
expected Person
}{
"Empty object": {
in: `{}`,
err: "missing fields: Name, City",
expected: Person{},
},
"Name missing": {
in: `{"City": "Berlin"}`,
err: "missing fields: Name",
expected: Person{City: "Berlin"},
},
"Age missing": {
in: `{"Name": "Friedrich"}`,
err: "missing fields: City",
expected: Person{Name: "Friedrich"},
},
"Unknown field": {
in: `{"Name": "Friedrich", "City": "Berlin", "Test": true}`,
err: "unknown fields: Test",
expected: Person{Name: "Friedrich", City: "Berlin"},
},
"OK": {
in: `{"Name": "Friedrich", "City": "Berlin"}`,
expected: Person{Name: "Friedrich", City: "Berlin"},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
var actual Person
r := strings.NewReader(c.in)
err := json.NewDecoder(r).Decode(&actual)
switch {
case err != nil && c.err == "":
t.Errorf("Expected no error but go %v", err)
case err == nil && c.err != "":
t.Errorf("Did not return expected error %v", c.err)
case err != nil && err.Error() != c.err:
t.Errorf("Expected error %q but got %v", c.err, err)
}
if !reflect.DeepEqual(c.expected, actual) {
t.Errorf("\nWant: %+v\nGot: %+v", c.expected, actual)
}
})
}
}
You could compare p with a empty struct, instead of comparing each field with nil.
// handle json did not match error
if p == Person{} {
return
}
Since Person{} will initialize with the 0 value of each field, this will result in each property that is pointers to be nil, strings will be "", ints will be 0, and so on.
I have a server that successfully opens a connection with a second server. The second server performs an action and I am trying to get it to reply to the first server with a JSON on the same connection.
package main
import (
"fmt"
"net"
"encoding/json"
)
type NewContainerJSON struct {
Action string `json:"Action"`
ContainerName string `json:"ContainerName"`
BaseServer string `json:"BaseServer"`
CMS string `json:"CMS"`
WebsiteName string `json:"WebsiteName"`
DBrootPWD string `json:"DBrootPWD"`
DBadminUname string `json:"DBadminUname"`
DBadminPWD string `json:"DBadminPWD"`
}
func main() {
service := "127.0.0.1:8081"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
conn, err := listener.Accept()
checkError(err)
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
var b NewContainerJSON
err = decoder.Decode(&b)
checkError(err)
fmt.Println(b.Action)
if b.Action == "createNew" {
fmt.Println("This works")
resp := []byte("And here's our repomse")
conn.Write(resp)
c := NewContainerJSON {
Action: "createdNewContainer",
ContainerName: "Test",
BaseServer: "Test",
CMS: "Test",
WebsiteName: "Test",
DBrootPWD: "Test",
DBadminUname: "Test",
DBadminPWD: "Test",
}
encoder := json.NewEncoder(conn)
if err := encoder.Encode(c); err != nil {
fmt.Println("encode.Encode error: ", err)
}
conn.Write(c)
}
}
func checkError(err error) {
if err != nil {
fmt.Println("An error occurred: ", err.Error())
}
}
I get following error on the line conn.Write(c)
cannot use c (type NewContainerJSON) as type []byte in argument to conn.Write
Two questions:
1: What exactly is this error saying? It seems to be complaining that 'c' cannot be used as a Byte when using the conn.Write function but shouldn't the json.Encoder convert the JSON to a format the conn.Write can use?
2: How exactly can I return a JSON back to the first server using the open connection?
The encoder writes the JSON encoding of c to conn on this line:
if err := encoder.Encode(c); err != nil {
That's all you need to do. Delete the call to conn.Write(c).
The error message is telling you that the value of c cannot be used as the argument to Write because of a type mismatch. A NewContainerJSON is not a []byte.
You first write a string to the connection by
resp := []byte("And here's our repomse")
conn.Write(resp)
This will make it error-prone on the client side. You'll need to read exactly the same amount of data before employ the json decoder on this connection.
If a connection is used for json communication, all the messages on this stream should be json.
So if you want send a message to notify, encode that message too:
encoder.Encode(string(resp))
Let's say I have the following Go struct on the server
type account struct {
Name string
Balance int
}
I want to call json.Decode on the incoming request to parse it into an 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 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.
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?
You can use a custom type with custom unmarshaling algorythm for your "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)
}
}
}
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.
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?
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).
A solution for this could be to use a type assertion by using a map to unmarshal the JSON data into:
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
The type assertion here is done using float64 because it is the default number type supported by Go's JSON decoder.
It should be noted that this use of interface{} is probably not worth the trouble.
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.
You could for example return a message of the sort:
cannot unmarshal string into Go value of type int near `"balance": "3"`
It would seem that here provides an implementation to work around this issue in Go only.
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.