# How to validate a payload schema

# Problem

Incoming request payloads in `JSON` format often requires validation.

Validators might get complex and messy.

We don’t want our codebase to be messy - at least we try not to.

# Solution

## JSON schema

The best way is to convert our `JSON` representation of the incoming request into the `JSON` schema with the help of one of the multiple converters online. I was using the [Free Online JSON to JSON Schema Converter](https://www.liquid-technologies.com/online-json-to-schema-converter).

From the input `JSON` payload:

```bash
{"id":"022eb326-6fa2-4932-8a5f-058a908616a4"}
```

We’re getting the `JSON` shema that looks like this for the mentioned input:

```bash
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  },
  "required": [
    "id"
  ]
}
```

## Schema validation

Now let’s get to the code.

The **validation** function:

```go
// ValidateSchema validates payload against JSON schema located in the schema file.
func ValidateSchema(payload map[string]any, schema string) error {
	// Let's read the schema file
	file, err := os.ReadFile(schema)
	if err != nil {
		return fmt.Errorf("os.ReadFile: %w", err)
	}

	// Prepare validators for schema...
	schemaLoader := gojsonschema.NewStringLoader(string(file))
	// ... and payload.
	payloadLoader := gojsonschema.NewGoLoader(payload)

	// Validate schema against payload:
	result, err := gojsonschema.Validate(schemaLoader, payloadLoader)
	if err != nil {
		return fmt.Errorf("gojsonschema.Validate: %w", err)
	}

	// If there was something wrong, communicate the errors:
	if !result.Valid() {
		errMsg := "JSON validation failed:\n"
		for _, desc := range result.Errors() {
			errMsg += fmt.Sprintf("- %s\n", desc)
		}
		return fmt.Errorf("%w: %s", ErrInvalidPayload, errMsg)
	}

	return nil
}
```

## Working application

Let’s put all the piecen together (file `main.go`):

```go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/google/uuid"
	"github.com/xeipuuv/gojsonschema"
)

var ErrInvalidPayload = errors.New("invalid payload")

type RequestPayload struct {
	ID uuid.UUID `json:"id"`
}

func main() {
	// Request payload.
	p, err := payload()
	if err != nil {
		log.Fatal(err)
	}

	// Validate incoming payload.
	if err = ValidateSchema(p, "schema/payload.json"); err != nil {
		log.Fatal(err)
	}

	// We're good!
	fmt.Println("Schema validated")
}

// ValidateSchema validates payload against JSON schema located in the schema file.
func ValidateSchema(payload map[string]any, schema string) error {
	// Let's read the schema file
	file, err := os.ReadFile(schema)
	if err != nil {
		return fmt.Errorf("os.ReadFile: %w", err)
	}

	// Prepare validators for schema...
	schemaLoader := gojsonschema.NewStringLoader(string(file))
	// ... and payload.
	payloadLoader := gojsonschema.NewGoLoader(payload)

	// Validate schema against payload:
	result, err := gojsonschema.Validate(schemaLoader, payloadLoader)
	if err != nil {
		return fmt.Errorf("gojsonschema.Validate: %w", err)
	}

	// If there was something wrong, communicate the errors:
	if !result.Valid() {
		errMsg := "JSON validation failed:\n"
		for _, desc := range result.Errors() {
			errMsg += fmt.Sprintf("- %s\n", desc)
		}
		return fmt.Errorf("%w: %s", ErrInvalidPayload, errMsg)
	}

	return nil
}

// payload returns sample request payload in map[string]any format.
func payload() (map[string]any, error) {
	r := &RequestPayload{ID: uuid.New()}

	payload, err := json.Marshal(r)
	if err != nil {
		return nil, fmt.Errorf("json.Marshal: %w", err)
	}

	var data map[string]any
	if err := json.Unmarshal(payload, &data); err != nil {
		return nil, fmt.Errorf("json.Unmarshal: %w", err)
	}

	return data, nil
}
```

```bash
➜  payloadschema git:(main) ✗ go run main.go
Schema validated
```

# Sources

* [Go code used in this article - Flashlabs’ KISS samples](https://github.com/flashlabs/kiss-samples/tree/main/payloadschema)
    
* [Free Online JSON to JSON Schema Converter](https://www.liquid-technologies.com/online-json-to-schema-converter)
    
* [UUID Generator](https://www.uuidgenerator.net/)
