Question
In a Go API, I query data, create a struct instance, and encode that struct as JSON for the response.
I want the client to be able to choose which fields are returned by passing a fields query parameter in the request. Depending on the fields value, the response should include only specific struct fields.
Is it possible to remove fields from a struct at runtime, or at least hide them dynamically in the JSON response?
omitempty is not enough here, because some fields may contain valid empty values and still need to be excluded based on the client's request.
Here is a simplified version of the structs:
type SearchResult struct {
Date string `json:"date"`
IdCompany int `json:"idCompany"`
Company string `json:"company"`
IdIndustry interface{} `json:"idIndustry"`
Industry string `json:"industry"`
IdContinent interface{} `json:"idContinent"`
Continent string `json:"continent"`
IdCountry interface{} `json:"idCountry"`
Country string `json:"country"`
IdState interface{} `json:"idState"`
State string `json:"state"`
IdCity interface{} `json:"idCity"`
City string `json:"city"`
}
type SearchResults struct {
NumberResults int `json:"numberResults"`
Results []SearchResult `json:"results"`
}
The response is encoded like this:
err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
What is the best way to support dynamic field selection in this situation?
Short Answer
By the end of this page, you will understand why Go structs cannot have fields removed at runtime, and how to return only selected JSON fields anyway. You will learn practical ways to build dynamic API responses using maps, filtered objects, and custom JSON handling.
Concept
In Go, a struct has a fixed shape defined at compile time. That means you cannot add or remove fields from a struct instance dynamically after the program is built.
However, JSON output is more flexible than the struct itself. Even if your Go struct always has the same fields, you can choose what to include in the JSON you send to the client.
This matters in real APIs because clients often want:
- smaller responses
- fewer unnecessary fields
- faster network transfers
- more control over what data they receive
There are three common ways to handle this in Go:
-
Encode a
map[string]interface{}instead of the struct- Build only the keys the client asked for.
- Good for highly dynamic output.
-
Create a filtered response object
- Convert your struct into a map or another struct before encoding.
- Good when you want more control and validation.
-
Implement custom JSON marshaling
- Advanced option using
MarshalJSON(). - Useful if the filtering logic belongs closely to the type.
- Advanced option using
The key idea is this:
- The struct stays fixed in Go
- The JSON response can still be dynamic
So the problem is not changing the struct itself. The problem is choosing a different JSON representation before encoding.
Mental Model
Think of a Go struct like a printed paper form with fixed boxes:
- one box for
date - one box for
company - one box for
country - and so on
You cannot tear boxes off the form at runtime.
But JSON output is like copying only some boxes from that form onto a new sheet before handing it to someone.
So instead of changing the original form, you create a custom outgoing version that includes only the requested fields.
Syntax and Examples
Fixed struct, dynamic JSON
A Go struct is fixed:
type SearchResult struct {
Date string `json:"date"`
Company string `json:"company"`
Country string `json:"country"`
}
You cannot do this:
// Not possible in Go:
// remove the Country field from result at runtime
But you can build a map dynamically:
result := SearchResult{
Date: "2024-01-01",
Company: "Acme",
Country: "USA",
}
response := map[string]interface{}{}
response["date"] = result.Date
response["company"] = result.Company
json.NewEncoder(w).Encode(response)
Output:
{
"date": "2024-01-01",
"company": "Acme"
}
Step by Step Execution
Consider this example:
package main
import (
"encoding/json"
"fmt"
"strings"
)
type SearchResult struct {
Date string `json:"date"`
Company string `json:"company"`
Country string `json:"country"`
}
func parseFields(raw string) map[string]bool {
fields := map[string]bool{}
for _, f := range strings.Split(raw, ",") {
f = strings.TrimSpace(f)
if f != "" {
fields[f] = true
}
}
return fields
}
func filterResult(r SearchResult, fields map[string]bool) map[string]interface{} {
out := map[string]interface{}{}
fields[] {
out[] = r.Date
}
fields[] {
out[] = r.Company
}
fields[] {
out[] = r.Country
}
out
}
{
result := SearchResult{
Date: ,
Company: ,
Country: ,
}
fields := parseFields()
filtered := filterResult(result, fields)
b, _ := json.Marshal(filtered)
fmt.Println((b))
}
Real World Use Cases
Dynamic field selection is useful in many real systems:
Public APIs
Clients may want only a few fields to reduce payload size.
Example:
GET /companies?fields=idCompany,company,country
Mobile apps
Mobile clients often need smaller responses to save bandwidth and improve performance.
Admin dashboards
One screen may need only summary fields, while another needs full detail.
Data export tools
Users may choose specific columns for CSV-like or JSON exports.
Microservices
One service may call another and request only the fields needed for a workflow.
Privacy and access control
Some fields can be excluded based on permissions, role, or subscription level.
Field filtering is not only about convenience. It can also improve efficiency and safety.
Real Codebase Usage
In real Go codebases, developers usually avoid trying to mutate struct definitions. Instead, they use a few common patterns.
Pattern 1: Build a response DTO
A DTO (Data Transfer Object) is a type or structure created specifically for output.
- internal model stays complete
- outgoing response is shaped for the API
Pattern 2: Use a whitelist of allowed fields
Never trust arbitrary field names from the client without validation.
var allowedFields = map[string]bool{
"date": true,
"idCompany": true,
"company": true,
"idIndustry": true,
"industry": true,
"country": true,
}
Then accept only known fields.
Pattern 3: Early validation
If the client asks for an unknown field, return a clear error early.
if !allowedFields[field] {
return errors.New("invalid field: " + field)
}
Pattern 4: Filter after fetching data
Often, developers query full rows from a database and then filter JSON output afterward.
Common Mistakes
1. Expecting omitempty to solve field selection
omitempty only removes fields when they have zero values.
type User struct {
Name string `json:"name,omitempty"`
}
If Name is an empty string, it may be omitted. But if the client does not want name, omitempty does not help unless the value also happens to be empty.
2. Trying to remove struct fields at runtime
Broken idea:
// This is not possible in Go.
// result.RemoveField("country")
Go structs are static.
3. Returning unknown fields without validation
Broken example:
fields := strings.Split(raw, ",")
// blindly trust every field name
This can cause inconsistent behavior and make your API harder to maintain.
Better:
if !allowedFields[f] {
fmt.Errorf(, f)
}
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Fixed struct with tags | Normal static APIs | Simple, type-safe, easy to read | Cannot dynamically exclude arbitrary fields based on request |
omitempty | Hiding zero-value fields | Very easy to use | Not for client-driven field selection |
map[string]interface{} | Dynamic responses | Flexible, easy to filter | Less type-safe, more manual code |
| Custom response struct | Controlled API output | Clear structure, easier to test | Can require many variants |
MarshalJSON() | Reusable custom encoding logic | Centralized JSON behavior |
Cheat Sheet
Quick rules
- Go structs are fixed at compile time.
- You cannot remove struct fields at runtime.
- You can choose which fields appear in JSON.
omitemptyhides zero values, not request-selected fields.- For dynamic API fields, use a filtered
map[string]interface{}or a custom response type.
Common pattern
fields := map[string]bool{
"date": true,
"company": true,
}
out := map[string]interface{}{}
if fields["date"] {
out["date"] = result.Date
}
if fields["company"] {
out["company"] = result.Company
}
Parse fields parameter
func parseFields(raw string) map[string]bool {
out := map[]{}
_, f := strings.Split(raw, ) {
f = strings.TrimSpace(f)
f != {
out[f] =
}
}
out
}
FAQ
Can I remove a field from a Go struct at runtime?
No. Struct fields are fixed when the program is compiled.
How do I hide fields in JSON based on a request parameter?
Build a new value for output, usually a map[string]interface{} or a custom response type containing only the requested fields.
Why is omitempty not enough?
Because omitempty depends on whether a value is empty, not on whether the client asked for the field.
Should I use reflection for this?
Usually not at first. Reflection can work, but explicit filtering code is simpler and easier to maintain.
Is using map[string]interface{} a good idea in APIs?
Yes, when the response shape is dynamic. Just validate input fields and keep the filtering logic clear.
What should happen if the client requests an invalid field?
A common approach is to return a 400 Bad Request with a message listing the invalid field.
Should I filter at the JSON layer or in the database query?
For simple implementations, filtering JSON output is fine. For performance-sensitive systems, you may later optimize the database query too.
Mini Project
Description
Build a small Go HTTP endpoint that returns company search results and supports a fields query parameter. The endpoint should always use the same Go struct internally, but dynamically return only the requested JSON fields. This demonstrates a practical pattern for real APIs where clients want control over the response payload.
Goal
Create an endpoint that accepts ?fields=... and returns only those fields in each result item.
Requirements
[ "Define a SearchResult struct with several JSON fields.", "Accept an optional fields query parameter containing comma-separated field names.", "Validate requested fields against an allowed list.", "Return the full response if no fields parameter is provided.", "Return filtered JSON objects when fields are specified." ]
Keep learning
Related questions
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.