qtag

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 8 Imported by: 0

README

qtag

qtag is a small, reflection-based Go package for decoding HTTP query parameters into strongly-typed structs using struct tags.

It is designed to be minimal and dependency-free.


Features

  • Zero Dependencies: Built entirely on the Go standard library.
  • Direct Integration: Seamlessly handles both *http.Request and url.Values.
  • Recursive Routing: Supports deep nesting via dot-notation and flat embedding.
  • Smart Fallbacks: Built-in support for default values and explicit field ignoring.
  • Interface Aware: Natively supports any type implementing encoding.TextUnmarshaler, such as time.Time and net.IP.
  • Strong Typing: Descriptive error reporting for type mismatches and invalid pointers.

Installation

go get github.com/swamphacks/qtag

Basic Usage

Add a qt tag to any struct field you want populated from the query string.

type QueryParams struct {
    Limit int64 `qt:"limit"`
    Page  int64 `qt:"page"`
}

Given a request:

https://example.com/some/endpoint?limit=200&page=2

You can decode it as follows:

var qp QueryParams
err := qtag.Decode(r, &qp)
if err != nil {
    // handle error
}

Using Unmarshal Directly

If you already have url.Values, you can call Unmarshal directly:

values := url.Values{
    "limit": {"100"},
    "page":  {"1"},
}

var qp QueryParams
err := qtag.Unmarshal(values, &qp)

Supported Types

We support all primitive values and anything that implements the standard encoding.TextUnmarshaler interface.

If a field implements encoding.TextUnmarshaler, it will be preferred over built-in parsing.


Tag Options

Basic Tag
Field string `qt:"field_name"`
Ignore a Field

Use - to explicitly ignore a field:

InternalID string `qt:"-"`

Note: Even if you supply a key or default, it will be ignored as long as - is present!!!

Default Values

Default values are supported via default=... in the tag:

type QueryParams struct {
    Limit int64 `qt:"limit,default=25"`
    Page  int64 `qt:"page,default=1"`
}

If the query parameter is missing or empty, the default value will be used. If no default is provided and the field is empty, we utilize that variable's zero value.

Note: Default parsing follows the same rules as normal decoding and must be valid for the field type.

Nested Structs

Since our parser is recursive, you can nest structs and parse flatly OR with a prefix.

For example:

type Pagination struct {
    Limit int64 `qt:"limit,default=25"`
    Page  int64 `qt:"page,default=1"`
}

type QueryParams struct {
    Paging Pagination
    UserID uuid.UUID `qt:"user_id"`
}

This will successfully parse https://example.com?limit=20&page=30&user_id=dcfff211-debd-4ff6-a07d-16d29050e6ae.

If you want namespaces, you can add a key to the struct to nest it with a namespace prefix.

For example:

type Options struct {
    Status *string `qt:"status,default=None"`
    Role *string `qt:"role"`
}

type QueryParams struct {
    Options Options `qt:"option"`
    UserID uuid.UUID `qt:"user_id"`
}

This will parse https://example.com?option.status=ADMITTED&option.role=ADMIN&user_id=dcfff211-debd-4ff6-a07d-16d29050e6ae


Error Handling

qtag returns descriptive errors when:

  • A non-struct pointer is provided
  • A value cannot be parsed into the target type
  • An unsupported field type is encountered

Example:

Cannot parse limit with value abc into an integer.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decode

func Decode[T any](r *http.Request, v *T) error

Decode is a convenience wrapper around Unmarshal for standard HTTP handlers. It extracts the query parameters directly from the *http.Request URL and parses them into the provided struct pointer.

Example:

func SearchHandler(w http.ResponseWriter, r *http.Request) {
	var filter SearchFilter
	if err := qtag.Decode(r, &filter); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	// ... use filter ...
}

func Unmarshal

func Unmarshal[T any](data url.Values, v *T) error

Unmarshal parses URL query parameters into a struct using reflection. It requires a valid pointer to a struct.

Behavior & Tag Routing

  • Basic Mapping: Define a `qt:"name"` tag to map a query parameter to a struct field.
  • Default Values: Add `,default=value` to provide a fallback if the parameter is missing from the URL. Example: `qt:"limit,default=20"`.
  • Ignored Fields: Use `qt:"-"` to explicitly tell the parser to skip a field entirely.
  • Tagged Nested Structs: If a struct field has a `qt` tag, it acts as a namespace using dot-notation. Example: `qt:"user"` creates keys like `?user.name=Alice`.
  • Untagged Nested Structs: If an embedded or nested struct has NO `qt` tag (or `qt:""`), its fields are "flattened" and inherit the parent's namespace.

Supported Types

  • Standard Types: string, bool, int/8/16/32/64, float32/64.
  • Custom Types: Any type that implements encoding.TextUnmarshaler is automatically supported. This includes standard library types like time.Time (which expects RFC 3339).

Common Pitfalls

  • Passing by Value: You MUST pass a pointer to the struct (e.g., &myStruct), otherwise the function will return an error.
  • Unexported Fields: Fields starting with a lowercase letter cannot be set via reflection and are silently skipped.
  • Struct Initialization: If you pre-populate your struct with values before calling Unmarshal, and a query parameter is missing, qtag will leave your pre-populated value alone unless you defined a default tag, which will overwrite it.

Example:

type Pagination struct {
	Limit int `qt:"limit,default=20"`
	Page  int `qt:"page,default=1"`
}

type SearchQuery struct {
	Pagination               // Untagged: inherits keys flatly (?limit=50)
	User       UserFilter    `qt:"user"` // Tagged: uses dot-notation (?user.name=Alice)
	InternalID string        `qt:"-"`
}

values, _ := url.ParseQuery("limit=50&user.name=Alice")
var q SearchQuery
err := qtag.Unmarshal(values, &q)

Types

type TagOptions

type TagOptions struct {
	Key     string  // "key" - Note: This will take the first key field in the tags array
	Default *string // "default=..."
	Ignore  bool    // "-"
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL