Autostrada
Autostrada ✕
Create a new codebase Get Autostrada Plus
Documentation Changelog Roadmap Give feedback
Login

Documentation › JSON API › Parsing JSON requests

Parsing JSON requests

HTTP requests containing a JSON body can be decoded using the functions int the internal/request package.

For example, if you expect a request body like containing a JSON object like {"Name": "Dave", "Age": 58}, you can decode it into an input struct by using the request.DecodeJSONObject() function like this:

func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Name string `json:"Name"`
        Age  int    `json:"Age"`
    }

    err := request.DecodeJSONObject(w, r, &input)
    if err != nil {
        var badRequestErr *request.BadRequestError
        switch {
        case errors.As(err, &badRequestErr):
            app.badRequest(w, r, err)
        default:
            app.serverError(w, r, err)
        }
        return
    }
    
    fmt.Println(input)
}

Important: The final argument to request.DecodeJSON() is the destination for the decoded JSON, and it must be a non-nil pointer. Passing anything else will cause a panic.

Additionally, if you are decoding into a struct, like in the example above, the destination struct fields must be exported (i.e. start with a capital letter).

If request.DecodeJSONObject() fails (for example, due to invalid or malformed JSON), it returns a user-friendly error with the type *request.BadRequestError that can be sent directly to the client using the app.badRequest() helper, as demonstrated in the example above.

The internal/request package also provides the request.DecodeJSONArray() function, that can be used to decode JSON arrays in the same way.

Validating JSON requests

The internal/validator package includes a simple (but powerful) validator.Validator type that you can use to carry out validation checks, and you can return any validation errors to the client with the app.failedValidation() helper.

Extending the example above:

func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Name      string    `json:"Name"`
        Age       int       `json:"Age"`
    }
    
    err := request.DecodeJSON(w, r, &input)
    if err != nil {
        var badRequestErr *request.BadRequestError
        switch {
        case errors.As(err, &badRequestErr):
            app.badRequest(w, r, err)
        default:
            app.serverError(w, r, err)
        }
        return
    }
    
    var vld validator.Validator

    vld.CheckField(input.Name != "", "Name", "Name is required")
    vld.CheckField(input.Age != 0, "Age", "Age is required")
    vld.CheckField(input.Age >= 21, "Age", "Age must be 21 or over")

    if input.HasErrors() {
        app.failedValidation(w, r, vld)
        return
    }

    fmt.Println(input)
}    

The app.failedValidation() helper will send a 422 status code along with any validation error messages. For the example above, the JSON response might look like this:

{
    "FieldErrors": {
        "Age": "Age must be 21 or over",
        "Name": "Name is required"
    }
}    

Alternatively, you may wish to embed the validator.Validator inside the decode destination, using the `json:"-"` struct tag so that nothing is accidentally decoded into it. For example:

func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Name      string    `json:"Name"`
        Age       int       `json:"Age"`
        validator.Validator `json:"-"`
    }

    err := request.DecodeJSON(w, r, &input)
    if err != nil {
        var badRequestErr *request.BadRequestError
        switch {
        case errors.As(err, &badRequestErr):
            app.badRequest(w, r, err)
        default:
            app.serverError(w, r, err)
        }
        return
    }

    input.CheckField(input.Name != "", "Name", "Name is required")
    input.CheckField(input.Age != 0, "Age", "Age is required")
    input.CheckField(input.Age >= 21, "Age", "Age must be 21 or over")

    if input.HasErrors() {
        app.failedValidation(w, r, input.Validator)
        return
    }

    fmt.Println(input)
}