Welcome To Golang By Example

Get JSON request body from a HTTP request in Go (Golang)

Table of Contents

Overview

json/encoding package contains methods that can be used to convert a  request body of an incoming HTTP request into a golang struct. Before we start just a word about the request body. The request body of an HTTP request is a sequence of bytes. It is the content-type of the HTTP request which denotes the format in which those bytes are represented and meant to be read back.  For a JSON request body, the content-type is

application/json

Also two things about golang struct that you need to know

type employee struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

Notice meta tags associated with each of the fields annotated with name as ‘json’. These meta fields are used to map keys in the JSON to the fields of the struct. For eg if we have JSON as

{
  "name" : "John",
  "age"  : 21
}

Then name key of the above JSON will map to the Name field of the employee struct and the age key in JSON will map to the Age field of the struct. Let’s say we have below struct and JSON

type employee struct {
	Name string `json:"n"`
	Age  int    `json:"ag"`
}

{
  "n" : "John",
  "age"  : 21
}

Then ‘n’ key of the JSON will map to the Name field of the struct and ‘ag’ key of the JSON will map to the Age field of the struct.

Below two methods of the json/encoding package can be used to get the JSON request body of an incoming request.

The second method is a preferred way to get the json request body for two reasons.

  1. Exported and
  2. Non-ignored fields of the struct.

There might be several issues that need to be taken care of when parsing the incoming JSON request body.

The Decode method can capture all of those issues and can return appropriate error messages as well except in one case when there is an unmarshaling error. Let’s see the error returned by the decode method in all cases

Imaging the incoming JSON need to converted into the employee struct we mentioned above

Issues

'{"Name":"John", "Age": 21, "Gender": "M"}'

The error returned by decode function will be

unknown field "Gender"
'{"Name": "John", "Age":}'

The error returned by the decode function will be

invalid character '}' looking for beginning of value
''

The error returned by decode function will be

EOF
'{"Name":"John", "Age": "21"}'

The error returned by decode function will be

json: cannot unmarshal string into Go struct field employee.age of type int

In this case, the error returned by the decode function is returning internal information which is not an appropriate error to be returned back to the client. It is also possible to catch such type of error and return an appropriate error to the client. And that is what we are doing in the below program as well. Just  checking if it is an unmarshaling and returning custom error message after that

Also in the code below we are setting the disallow unknown fields option on the decoder so that any extra fields in the incoming JSON body results in an error.

decore.DisallowUnknownFields()

Example

Below is full program for the same.

package main

import (
	"encoding/json"
	"errors"
	"net/http"
)

type employee struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	createEmployeeHanlder := http.HandlerFunc(createEmployee)
	http.Handle("/employee", createEmployeeHanlder)
	http.ListenAndServe(":8080", nil)
}

func createEmployee(w http.ResponseWriter, r *http.Request) {
	headerContentTtype := r.Header.Get("Content-Type")
	if headerContentTtype != "application/json" {
		errorResponse(w, "Content Type is not application/json", http.StatusUnsupportedMediaType)
		return
	}
	var e employee
	var unmarshalErr *json.UnmarshalTypeError

	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	err := decoder.Decode(&e)
	if err != nil {
		if errors.As(err, &unmarshalErr) {
			errorResponse(w, "Bad Request. Wrong Type provided for field "+unmarshalErr.Field, http.StatusBadRequest)
		} else {
			errorResponse(w, "Bad Request "+err.Error(), http.StatusBadRequest)
		}
		return
	}
	errorResponse(w, "Success", http.StatusOK)
	return
}

func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(httpStatusCode)
	resp := make(map[string]string)
	resp["message"] = message
	jsonResp, _ := json.Marshal(resp)
	w.Write(jsonResp)
}

Run the above file. It will trigger a server that will listen to port 8080. After the server is running, let’s make the API calls for some of the scenarios we have discussed above.

curl -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21}'

Output

Response Code: 200
Response Body: {"message":"Success"}
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21, "Gender": "M"}'

Output

Response Code: 400
Response Body: {"message":"Bad Request json: unknown field \"Gender\""}
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name": "John", "Age":}'

Output

Response Code: 400
Response Body: {"message":"Bad Request invalid character '}' looking for beginning of value"}
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee

Output

Response Code: 400
Response Body: {"message":"Bad Request EOF"}
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": "21"}'

Output

Response Code: 400
Response Body: {"message":"Bad Request. Wrong Type provided for field age"}
curl -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21}'

Output

Response Code: 415 Unsupported Media Type
Response Body: {"message":"Content Type is not application/json"}