I recently had an interesting experience while playing around with Go’s structs and JSON marshaling. Take a look at the example below:
package main
import (
"fmt"
"encoding/json"
)
type Trip struct {
tripId string
tripType string
}
func main() {
trip := Trip{tripType: "air"}
fmt.Println(trip)
tripJson, err := json.Marshal(trip)
fmt.Println(err)
fmt.Println(string(tripJson))
}
Below is the output:
{ air}
<nil>
{}
Did you notice the issue? Within the same package, printing trip returns the data ({ air}) correctly, but tripJson returns empty ({}) without any error. This happens because the json package silently ignores unexported (private) fields in your struct.
Unexported / Private Fields
In Go, any method or variable starting with a lowercase letter is considered unexported (private). However, the concept of “privacy” in Go is package-level.
In the case above, json is a separate package. Therefore, it does not have access to get or set the unexported fields of the Trip struct.
Fixing the Struct
To make the fields accessible to the json package, you must export them by starting their names with an uppercase letter:
package main
import (
"fmt"
"encoding/json"
)
type Trip struct {
TripId string
TripType string
}
func main() {
trip := Trip{TripType: "air"}
tripJson, _ := json.Marshal(trip)
fmt.Println(string(tripJson))
}
Now, the output will be:
{"TripId":"","TripType":"air"}
JSON Marshaling and Private Fields with Struct Tags
package main
import (
"fmt"
"encoding/json"
)
type Trip struct {
tripId string `json:"trip_id"`
tripType string `json:"trip_type"`
}
func main() {
trip := Trip{tripType: "air"}
fmt.Println(trip)
tripJson, err := json.Marshal(trip)
fmt.Println(err)
fmt.Println(string(tripJson))
}
In the code above, the fields are still unexported despite having JSON tags. Running go vet will throw warnings:
➜ go vet trip/trip.go
trip/trip.go:9:4: struct field tripId has json tag but is not exported
trip/trip.go:10:4: struct field tripType has json tag but is not exported
It is always a best practice to use JSON tags even when the keys are the same as the field names, as this helps prevent mistakes. Using go vet is a great way to catch cases where you might be trying to use unexported fields with JSON tags.
Note: The Go compiler will still compile and run the code without error, but the JSON output will remain empty.