NetLife Guru

Open source Go packages for fast, maintainable web systems. Built with a documentation-first approach.

Product
OverviewGolang packagesNews
Documentation
DocumentationGo LoggerGo RouterGo DB Form
Company
OverviewContactNewsGitHub
Community / Support
Supportinfo@netlife.guru
© 2026 NetLife Guru. All rights reserved.
GitHubinfo@netlife.guru
NetLife GuruNetLife GuruNetLife Guru
NetLife GuruNetLife GuruNetLife Guru
OverviewDocumentationNewsSupportContact

Golang packages

About
Optional ValidationOptional String ValidationOptional Integer ValidationOptional Float64 ValidationOptional Time ValidationOptional Pointer ValidationOptional Slice Validation
Form ValidatorOptional Validation

Optional Slice Validation

Learn how optional slice validation works in NLG Form using OptionalSlice, optional arrays, collection validation, item limits, and reusable validation pipelines.

OptionalSlice allows slice validation rules to run only when a collection contains items.

This is useful for optional arrays, tags, permissions, categories, metadata lists, and partial update APIs where empty collections should be ignored instead of rejected.

Why OptionalSlice Exists

Many APIs contain collections that are optional.

Examples include:

  • tags
  • categories
  • permissions
  • feature lists
  • metadata arrays
  • selected filters
  • user groups
  • shopping cart items
  • labels
  • batch operations

Without optional validation, every collection would require additional conditional logic.

OptionalSlice keeps validation pipelines clean and reusable.

TL;DR

HelperDescription
optional.OptionalSlice(field, rules...)Runs nested rules only when the slice contains items
optional.OptionalSliceWith(field, fn, rules...)Runs validation only when a custom condition passes
optional.OptionalSliceLen(field, n, rules...)Runs validation only when the slice length matches n

Defining Optional Slice Fields

Optional slice validation starts with typed slice fields.

type OptionalSliceRequest struct {
	Tags        []string `json:"tags"`
	Permissions []string `json:"permissions"`
	Ids         []int    `json:"ids"`
}

Field definitions use form.Slice.

OptionalSliceForm := struct {
	Tags        form.SliceField[OptionalSliceRequest, string]
	Permissions form.SliceField[OptionalSliceRequest, string]
	Ids         form.SliceField[OptionalSliceRequest, int]
}{
	Tags: form.Slice[OptionalSliceRequest]("tags", func(r *OptionalSliceRequest) []string {
		return r.Tags
	}),
	Permissions: form.Slice[OptionalSliceRequest]("permissions", func(r *OptionalSliceRequest) []string {
		return r.Permissions
	}),
	Ids: form.Slice[OptionalSliceRequest]("ids", func(r *OptionalSliceRequest) []int {
		return r.Ids
	}),
}

Applying OptionalSlice

OptionalSlice executes nested validation rules only when the slice contains items.

return form.Schema[OptionalSliceRequest]{
	optional.OptionalSlice(
		OptionalSliceForm.Tags.Field,
		rules.MinItems(OptionalSliceForm.Tags, 2),
		rules.UniqueItems(OptionalSliceForm.Tags),
	),
}

Validation behavior:

InputResult
[]skipped
["go"]invalid
["go", "api"]valid

OptionalSliceWith

OptionalSliceWith allows custom conditional collection validation.

Example:

optional.OptionalSliceWith(
	OptionalSliceForm.Tags.Field,
	func(v []string) bool {
		return len(v) > 0
	},
	rules.UniqueItems(OptionalSliceForm.Tags),
)

Typical use cases:

  • advanced filtering
  • conditional array validation
  • staged onboarding
  • dynamic API rules

OptionalSliceLen

OptionalSliceLen executes validation only when the slice length matches a specific value.

Example:

optional.OptionalSliceLen(
	OptionalSliceForm.Tags.Field,
	1,
	rules.ContainsItem(OptionalSliceForm.Tags, "featured"),
)

Typical use cases:

  • conditional tagging
  • restricted batch operations
  • workflow constraints
  • dynamic collection rules

Combining Slice Rules

Optional slice validation works with all regular slice validation rules.

Example:

optional.OptionalSlice(
	OptionalSliceForm.Tags.Field,
	rules.MinItems(OptionalSliceForm.Tags, 2),
	rules.MaxItems(OptionalSliceForm.Tags, 5),
	rules.UniqueItems(OptionalSliceForm.Tags),
)

This creates reusable collection validation pipelines.

Complete Example

schema.go

package main

import (
	"github.com/netlifeguru/form"
	"github.com/netlifeguru/form/optional"
	"github.com/netlifeguru/form/rules"
)

const (
	CodeTagsMinItems       = form.Code("tags_min_items")
	CodePermissionsUnique  = form.Code("permissions_unique")
	CodeIdsBetween         = form.Code("ids_between")
)

type OptionalSliceRequest struct {
	Tags        []string `json:"tags"`
	Permissions []string `json:"permissions"`
	Ids         []int    `json:"ids"`
}

func OptionalSliceSchema() form.Schema[OptionalSliceRequest] {
	OptionalSliceForm := struct {
		Tags        form.SliceField[OptionalSliceRequest, string]
		Permissions form.SliceField[OptionalSliceRequest, string]
		Ids         form.SliceField[OptionalSliceRequest, int]
	}{
		Tags: form.Slice[OptionalSliceRequest]("tags", func(r *OptionalSliceRequest) []string {
			return r.Tags
		}),
		Permissions: form.Slice[OptionalSliceRequest]("permissions", func(r *OptionalSliceRequest) []string {
			return r.Permissions
		}),
		Ids: form.Slice[OptionalSliceRequest]("ids", func(r *OptionalSliceRequest) []int {
			return r.Ids
		}),
	}

	return form.Schema[OptionalSliceRequest]{
		optional.OptionalSlice(
			OptionalSliceForm.Tags.Field,
			rules.MinItemsWithCode(OptionalSliceForm.Tags, 2, CodeTagsMinItems),
		),

		optional.OptionalSlice(
			OptionalSliceForm.Permissions.Field,
			rules.UniqueItemsWithCode(OptionalSliceForm.Permissions, CodePermissionsUnique),
		),

		optional.OptionalSlice(
			OptionalSliceForm.Ids.Field,
			rules.ItemsBetweenWithCode(OptionalSliceForm.Ids, 1, 5, CodeIdsBetween),
		),
	}
}

main.go

package main

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"os"

	"github.com/netlifeguru/form"
	"github.com/netlifeguru/form/httpform"
	"github.com/netlifeguru/router"
)

func main() {
	r := router.New()

	r.HandleFunc("/optional-slice", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
		var in OptionalSliceRequest

		if !httpform.BindAndValidate(w, req, &in, OptionalSliceSchema(), 1<<20) {
			fmt.Println("optional slice validation failed")
			return
		}

		fmt.Println("optional slice validation passed:", in)

		w.Header().Set("Content-Type", "application/json")

		_ = json.NewEncoder(w).Encode(map[string]any{
			"message": "optional slice validation passed",
			"data":    in,
		})
	})

	validPayload := map[string]any{
		"tags":        []string{"go", "api"},
		"permissions": []string{"read", "write"},
		"ids":         []int{1, 2, 3},
	}

	invalidPayload := map[string]any{
		"tags":        []string{"go"},
		"permissions": []string{"read", "read"},
		"ids":         []int{1, 2, 3, 4, 5, 6},
	}

	skippedPayload := map[string]any{
		"tags":        []string{},
		"permissions": []string{},
		"ids":         []int{},
	}

	fmt.Println("\n--- Valid request ---")
	form.SendTestPost(":8080/optional-slice", validPayload)

	fmt.Println("\n--- Invalid request ---")
	form.SendTestPost(":8080/optional-slice", invalidPayload)

	fmt.Println("\n--- Skipped validation request ---")
	form.SendTestPost(":8080/optional-slice", skippedPayload)

	if err := r.ListenAndServe(8080); err != nil {
		slog.Error("failed to start server", "error", err)
		os.Exit(1)
	}
}

Validation Flow

Optional slice validation behaves like this:

empty slice
    ↓
validation skipped
non-empty slice
    ↓
nested rules executed

Notes

  • Optional slice validation runs only when the slice contains items.
  • Empty collections are intentionally skipped and are not validation failures.
  • Optional slice validation is useful for PATCH APIs and partial updates.
  • Optional slice validation works with all standard slice rules.
  • Collection validation remains reusable and transport-independent.
  • Optional slice validation helps keep schemas explicit and composable.
  • Slice validation is especially useful for APIs that expose optional tags, permissions, metadata, or batch payloads.

Optional Pointer Validation

Learn how optional pointer validation works in NLG Form using OptionalPtr, nullable fields, PATCH APIs, nested objects, and reusable conditional validation pipelines.

Conditional Validation

Learn how conditional validation works in NLG Form using conditional rules, runtime predicates, When helpers, and reusable validation pipelines.

On this page

Why OptionalSlice ExistsTL;DRDefining Optional Slice FieldsApplying OptionalSliceOptionalSliceWithOptionalSliceLenCombining Slice RulesComplete Exampleschema.gomain.goValidation FlowNotes