Troubleshooting
8 minute read
Solutions to common issues, frequently asked questions, and debugging strategies for the binding package.
Common Issues
Field Not Binding
Problem: Field remains zero value after binding
Possible Causes:
Field is unexported
// Wrong - unexported field type Request struct { name string `json:"name"` // Won't bind } // Correct type Request struct { Name string `json:"name"` }Tag name doesn’t match source key
// JSON: {"username": "alice"} type Request struct { Name string `json:"name"` // Wrong tag name } // Correct type Request struct { Name string `json:"username"` // Matches JSON key }Wrong tag type for source
// Binding from query parameters type Request struct { Name string `json:"name"` // Wrong - should be `query:"name"` } // Correct type Request struct { Name string `query:"name"` }Source doesn’t contain the key
// URL: ?page=1 type Params struct { Page int `query:"page"` Limit int `query:"limit"` // Missing in URL } // Solution: Use default type Params struct { Page int `query:"page" default:"1"` Limit int `query:"limit" default:"20"` }
Type Conversion Errors
Problem: Error like “cannot unmarshal string into int”
Solutions:
Check source data type
// JSON: {"age": "30"} <- string instead of number type User struct { Age int `json:"age"` } // Error: cannot unmarshal string into intFix: Ensure JSON sends number:
{"age": 30}Use string type and convert manually
type User struct { AgeStr string `json:"age"` } user, err := binding.JSON[User](data) age, _ := strconv.Atoi(user.AgeStr)Register custom converter
binder := binding.MustNew( binding.WithConverter[MyType](parseMyType), )
Slice Not Parsing
Problem: Slice remains empty or has unexpected values
Cause: Wrong slice mode for input format
// URL: ?tags=go,rust,python
type Params struct {
Tags []string `query:"tags"`
}
// With default mode (SliceRepeat)
params, _ := binding.Query[Params](values)
// Result: Tags = ["go,rust,python"] <- Wrong!
Solution: Use CSV mode
params, err := binding.Query[Params](values,
binding.WithSliceMode(binding.SliceCSV))
// Result: Tags = ["go", "rust", "python"] <- Correct!
Or use repeated parameters:
// URL: ?tags=go&tags=rust&tags=python
params, _ := binding.Query[Params](values) // Default mode works
JSON Parsing Errors
Problem: “unexpected end of JSON input” or “invalid character”
Causes:
Malformed JSON
{"name": "test" // Missing closing braceSolution: Validate JSON syntax
Empty body
// Body is empty but expecting JSON user, err := binding.JSON[User](r.Body) // Error: unexpected end of JSON inputSolution: Check if body is empty first
body, err := io.ReadAll(r.Body) if len(body) == 0 { return errors.New("empty body") } user, err := binding.JSON[User](body)Body already consumed
body, _ := io.ReadAll(r.Body) // Consumes body // ... some code ... user, err := binding.JSON[User](r.Body) // Error: body emptySolution: Restore body
body, _ := io.ReadAll(r.Body) r.Body = io.NopCloser(bytes.NewReader(body)) user, err := binding.JSON[User](body)
Unknown Field Errors
Problem: Error in strict mode for valid JSON
Cause: JSON contains fields not in struct
// JSON: {"name": "alice", "extra": "field"}
type User struct {
Name string `json:"name"`
}
user, err := binding.JSON[User](data, binding.WithStrictJSON())
// Error: json: unknown field "extra"
Solutions:
Add field to struct
type User struct { Name string `json:"name"` Extra string `json:"extra"` }Remove strict mode
user, err := binding.JSON[User](data) // Ignores extra fieldsUse interface{} for unknown fields
type User struct { Name string `json:"name"` Extra map[string]interface{} `json:"-"` }
Pointer vs Value Confusion
Problem: Can’t distinguish between “not provided” and “zero value”
Example:
type UpdateRequest struct {
Age int `json:"age"`
}
// JSON: {"age": 0}
// Can't tell if: 1) User wants to set age to 0, or 2) Field not provided
Solution: Use pointers
type UpdateRequest struct {
Age *int `json:"age"`
}
// JSON: {"age": 0} -> Age = &0 (explicitly set to zero)
// JSON: {} -> Age = nil (not provided)
// JSON: {"age": null} -> Age = nil (explicitly null)
Default Values Not Applied
Problem: Default value doesn’t work
Cause: Defaults only apply when field is missing, not for zero values
type Params struct {
Page int `query:"page" default:"1"`
}
// URL: ?page=0
params, _ := binding.Query[Params](values)
// Result: Page = 0 (not 1, because 0 was provided)
Solution: Use pointer to distinguish nil from zero
type Params struct {
Page *int `query:"page" default:"1"`
}
// URL: ?page=0 -> Page = &0
// URL: (no page) -> Page = &1 (default applied)
Nested Struct Not Binding
Problem: Nested struct fields remain zero
Example:
// JSON: {"user": {"name": "alice", "age": 30}}
type Request struct {
User struct {
Name string `json:"name"`
Age int `json:"age"`
} `json:"user"`
}
req, err := binding.JSON[Request](data)
// Works correctly
For query parameters, use dot notation:
// URL: ?user.name=alice&user.age=30
type Request struct {
User struct {
Name string `query:"user.name"`
Age int `query:"user.age"`
}
}
Time Parsing Errors
Problem: “parsing time … as …: cannot parse”
Cause: Time format doesn’t match any default layouts
// JSON: {"created": "01/02/2006"}
type Request struct {
Created time.Time `json:"created"`
}
// Error: parsing time "01/02/2006"
Solution: Add custom time layout
binder := binding.MustNew(
binding.WithTimeLayouts(
append(binding.DefaultTimeLayouts, "01/02/2006")...,
),
)
req, err := binder.JSON[Request](data)
Memory Issues
Problem: Out of memory or slow performance
Causes:
Large payloads without limits
// No limit - vulnerable to memory attack user, err := binding.JSON[User](r.Body)Solution: Set size limits
user, err := binding.JSON[User](r.Body, binding.WithMaxBytes(1024*1024), // 1MB limit binding.WithMaxSliceLen(1000), binding.WithMaxMapSize(500), )Not using streaming for large data
// Bad - loads entire body into memory body, _ := io.ReadAll(r.Body) user, err := binding.JSON[User](body)Solution: Stream from reader
user, err := binding.JSONReader[User](r.Body)
Header Case Sensitivity
Problem: Header not binding
Cause: HTTP headers are case-insensitive but tag must match exact case
// Header: x-api-key: secret
type Request struct {
APIKey string `header:"X-API-Key"` // Still works!
}
// Headers are matched case-insensitively
Note: The binding package handles case-insensitive header matching automatically.
Multi-Source Precedence Issues
Problem: Wrong source value used
Example:
// Query: ?user_id=1
// JSON: {"user_id": 2}
type Request struct {
UserID int `query:"user_id" json:"user_id"`
}
req, err := binding.Bind[Request](
binding.FromQuery(values), // user_id = 1
binding.FromJSON(body), // user_id = 2 (overwrites!)
)
// Result: UserID = 2
Solutions:
Change source order (last wins)
req, err := binding.Bind[Request]( binding.FromJSON(body), // user_id = 2 binding.FromQuery(values), // user_id = 1 (overwrites!) ) // Result: UserID = 1Use first-wins strategy
req, err := binding.Bind[Request]( binding.WithMergeStrategy(binding.MergeFirstWins), binding.FromQuery(values), // user_id = 1 (wins!) binding.FromJSON(body), // user_id = 2 (ignored) ) // Result: UserID = 1
Frequently Asked Questions
Q: How do I validate required fields?
A: Use the rivaas.dev/validation package after binding:
import "rivaas.dev/validation"
type Request struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"required,min=18"`
}
req, err := binding.JSON[Request](data)
if err != nil {
return err
}
// Validate after binding
if err := validation.Validate(req); err != nil {
return err
}
Q: Can I bind to non-struct types?
A: Yes, but only for certain types:
// Array
type Batch []CreateUserRequest
batch, err := binding.JSON[Batch](data)
// Map
type Config map[string]string
config, err := binding.JSON[Config](data)
// Primitive (less useful)
var count int
err := binding.JSONTo([]byte("42"), &count)
Q: How do I handle optional vs. required fields?
A: Combine binding with validation:
type Request struct {
Name string `json:"name" validate:"required"`
Email *string `json:"email" validate:"omitempty,email"`
}
// Name is required (validation)
// Email is optional (pointer) but if provided must be valid (validation)
Q: Can I use custom JSON field names?
A: Yes, use the json tag:
type User struct {
ID int `json:"user_id"` // Maps to "user_id" in JSON
FullName string `json:"full_name"` // Maps to "full_name" in JSON
}
Q: How do I bind from multiple query parameters to one field?
A: Use tag aliases:
type Request struct {
UserID int `query:"user_id,id,uid"` // Accepts any of these
}
// Works with: ?user_id=123, ?id=123, or ?uid=123
Q: Can I use both JSON and form binding?
A: Yes, use multi-source binding:
type Request struct {
Name string `json:"name" form:"name"`
}
req, err := binding.Bind[Request](
binding.FromJSON(r.Body),
binding.FromForm(r.Form),
)
Q: How do I debug binding issues?
A: Use event hooks:
binder := binding.MustNew(
binding.WithEvents(binding.Events{
FieldBound: func(name, tag string) {
log.Printf("Bound %s from %s", name, tag)
},
UnknownField: func(name string) {
log.Printf("Unknown field: %s", name)
},
Done: func(stats binding.Stats) {
log.Printf("%d fields, %d errors, %v",
stats.FieldsBound, stats.ErrorCount, stats.Duration)
},
}),
)
Q: Is binding thread-safe?
A: Yes, all operations are thread-safe. The struct cache uses lock-free reads and synchronized writes.
Q: How do I bind custom types?
A: Register a converter:
import "github.com/google/uuid"
binder := binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
)
Or implement encoding.TextUnmarshaler:
type MyType string
func (m *MyType) UnmarshalText(text []byte) error {
*m = MyType(string(text))
return nil
}
Q: Can I bind from environment variables?
A: Not directly, but you can create a custom getter:
type EnvGetter struct{}
func (g *EnvGetter) Get(key string) string {
return os.Getenv(key)
}
func (g *EnvGetter) GetAll(key string) []string {
if val := os.Getenv(key); val != "" {
return []string{val}
}
return nil
}
func (g *EnvGetter) Has(key string) bool {
_, exists := os.LookupEnv(key)
return exists
}
// Use with RawInto
config, err := binding.RawInto[Config](&EnvGetter{}, "env")
Q: What’s the difference between JSON and JSONReader?
A:
JSON: Takes[]byte, entire data in memoryJSONReader: Takesio.Reader, streams data
Use JSONReader for large payloads (>1MB) to reduce memory usage.
Q: How do I handle API versioning?
A: Use different struct types per version:
type CreateUserRequestV1 struct {
Name string `json:"name"`
}
type CreateUserRequestV2 struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
// Route to appropriate handler based on version header
Debugging Strategies
1. Enable Debug Logging
import "log/slog"
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})))
2. Inspect Raw Request
// Save body for debugging
body, _ := io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewReader(body))
log.Printf("Raw body: %s", string(body))
log.Printf("Content-Type: %s", r.Header.Get("Content-Type"))
req, err := binding.JSON[Request](r.Body)
3. Use Curl to Test
# Test JSON binding
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"alice","age":30}'
# Test query parameters
curl "http://localhost:8080/users?page=2&limit=50"
# Test headers
curl -H "X-API-Key: secret" http://localhost:8080/users
4. Write Unit Tests
func TestBinding(t *testing.T) {
payload := `{"name":"test","age":30}`
user, err := binding.JSON[User]([]byte(payload))
if err != nil {
t.Fatalf("binding failed: %v", err)
}
if user.Name != "test" {
t.Errorf("expected name=test, got %s", user.Name)
}
}
Getting Help
If you’re still stuck:
- Check the examples: Binding Guide
- Review API docs: API Reference
- Search GitHub issues: rivaas-dev/rivaas/issues
- Ask for help: Open a new issue with:
- Minimal reproducible example
- Expected vs. actual behavior
- Relevant logs/errors
See Also
- API Reference - Complete API documentation
- Options - Configuration options
- Performance - Optimization tips
- Binding Guide - Step-by-step tutorials
For more examples and patterns, see the Binding Guide.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.