How do I exclude a field when converting a struct to JSON in Go?
I have the following structs:
type Object struct {
UpdatedAt time.Time
}
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
When running the code:
myOb := MyObject{}
myOb.UpdatedAt = time.Now()
myOb.Price = 100
json.Marshal(myOb)
I get the following result:
{
"UpdatedAt": "0001-01-01T00:00:00Z",
"Price": 100
}
How do I properly exclude the UpdatedAt field from the JSON output? Why is the field with the json:"-" tag still included in the output?
Excluding the UpdatedAt Field from JSON in Go
To exclude the UpdatedAt field from JSON when converting a struct in Go, you need to use the correct combination of tags and methods. In your case, the problem occurs due to field resolution in embedded structs and the order in which they are processed.
Table of Contents
- Main Problem and Solution
- How Field Resolution Works in Go
- Ways to Exclude Fields from JSON
- Advanced Techniques for JSON Output Control
- Code Examples and Best Practices
- Conclusion
Main Problem and Solution
In your code, the problem occurs because Go processes the embedded Object struct containing the UpdatedAt field first during JSON marshaling. Even though your local UpdatedAt field has the json:"-" tag, the embedded struct has already added its field to the result.
The correct solution is to explicitly specify which field should be used or modify the structure:
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"` // This field will be ignored
Price int
}
To solve your problem, use one of these approaches:
- Create an alias for the embedded field:
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
// Explicitly specify which field should be in JSON
UpdatedAtField time.Time `json:"updated_at"`
}
- Use the
json.Marshalerinterface for complete control over the serialization process.
How Field Resolution Works in Go
According to the Go documentation, Go uses the following rules for field resolution during JSON marshaling:
- If there are multiple fields at the same level and only one has an explicit JSON name, that field takes priority and the others are excluded
- For embedded structs, Go similarly applies field visibility rules
- Processing order is important - embedded structs are processed before fields of the main type
This explains why your json:"-" tag doesn’t work as expected - the embedded Object struct has already added its UpdatedAt field before the system processes the tag on your local field.
Ways to Exclude Fields from JSON
1. Using the json:"-" Tag
The basic way to exclude a field from JSON output:
type User struct {
ID int `json:"id"`
Password string `json:"-"` // This field will never be in JSON
}
2. Using omitempty
Exclude a field only when it’s absent or has a zero value:
type Product struct {
Name string `json:"name"`
Price float64 `json:"price,omitempty"`
Discount float64 `json:"discount,omitempty"`
}
3. Reflection for Selective Marshaling
As mentioned in this Stack Overflow answer, you can use the reflect package for selective field inclusion:
func MarshalSelective(v interface{}, fieldsToInclude []string) ([]byte, error) {
val := reflect.ValueOf(v)
// Logic for selective field inclusion
}
4. Custom Marshaling Methods
Implement the json.Marshaler interface:
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
func (m MyObject) MarshalJSON() ([]byte, error) {
type Alias MyObject
return json.Marshal(struct {
Alias
UpdatedAt time.Time `json:"updated_at"`
}{
Alias: (Alias)(m),
UpdatedAt: m.UpdatedAt,
})
}
Advanced Techniques for JSON Output Control
1. Using Composition with Custom Types
As described in the Boldly Go article, you can create a local type with the required fields:
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
// For marshaling, create only the needed fields
type MyObjectMarshal struct {
UpdatedAt time.Time `json:"updated_at"`
Price int `json:"price"`
}
func (m *MyObject) MarshalJSON() ([]byte, error) {
return json.Marshal(MyObjectMarshal{
UpdatedAt: m.UpdatedAt,
Price: m.Price,
})
}
2. Managing Field Name Conflicts
When fields with the same name exist in different structs, you can use aliases:
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
UpdatedAtFromObject time.Time `json:"updated_at_from_object"`
}
3. Using Libraries for JSON Control
There are third-party libraries that provide more flexible JSON output control, such as:
https://github.com/mitchellh/mapstructurehttps://github.com/fatih/structs
Code Examples and Best Practices
Example 1: Proper Handling of Embedded Structs
type Object struct {
UpdatedAt time.Time
}
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
// Proper usage
func main() {
myOb := MyObject{}
myOb.UpdatedAt = time.Now()
myOb.Price = 100
// Explicitly specify which field should be in JSON
type Alias MyObject
result, _ := json.Marshal(struct {
Alias
UpdatedAt time.Time `json:"updated_at"`
}{
Alias: (Alias)(myOb),
UpdatedAt: myOb.UpdatedAt,
})
fmt.Println(string(result))
}
Example 2: Fully Custom Marshaling
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
func (m MyObject) MarshalJSON() ([]byte, error) {
type Alias MyObject
data := struct {
Alias
UpdatedAt time.Time `json:"updated_at,omitempty"`
Price int `json:"price"`
}{
Alias: (Alias)(m),
UpdatedAt: m.UpdatedAt,
Price: m.Price,
}
return json.Marshal(data)
}
Example 3: Using Interfaces for Flexibility
type JSONExclude interface {
ShouldExclude(fieldName string) bool
}
type MyObject struct {
Object
UpdatedAt time.Time `json:"-"`
Price int
}
func (m MyObject) ShouldExclude(fieldName string) bool {
return fieldName == "UpdatedAt"
}
func MarshalWithExclude(v interface{}) ([]byte, error) {
val := reflect.ValueOf(v)
// Implementation of custom marshaling with exclusion checks
}
Conclusion
Excluding fields from JSON output in Go requires understanding how embedded structs are processed and how field name conflicts are resolved. The main takeaways are:
- The
json:"-"tag only works for fields that don’t conflict with fields from embedded structs - For managing complex structs, use custom
MarshalJSONmethods - When working with embedded structs, always explicitly specify which fields should be included in the JSON
- For complex scenarios, consider using reflection or third-party libraries
The recommended practice for your case is to implement a custom MarshalJSON method that will explicitly control which fields are included in the JSON output. This gives you complete control over the serialization process and helps avoid unexpected behavior when working with embedded structs.
Sources
- Go JSON package documentation - encoding/json/v2
- Stack Overflow - Removing fields from struct or hiding them in JSON Response
- Boldly Go - JSON Tricks: Extending an Embedded Marshaler
- GitHub Issue - encoding/json: tag
json:"-"doesn’t hide an embedded field - Bruno Scheufler - Go Embeds and JSON
- Gopher Dojo - Go JSON (Un)Marshalling, Missing Fields and Omitempty