Question
I am trying to convert a Go struct to JSON using Go's encoding/json package, but the result is always an empty object: {}.
Here is the code:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
name string
}
func main() {
user := &User{name: "Frank"}
b, err := json.Marshal(user)
if err != nil {
fmt.Printf("Error: %s", err)
return
}
fmt.Println(string(b))
}
When I run it, the output is:
{}
Why does json.Marshal produce {} instead of including the name field, and how do I fix it?
Short Answer
By the end of this page, you will understand why json.Marshal ignores some struct fields in Go, how exported vs unexported fields work, and how to correctly produce JSON using exported field names and optional struct tags.
Concept
In Go, the encoding/json package only reads exported struct fields when converting a struct to JSON.
A field is exported if its name starts with an uppercase letter:
type User struct {
Name string
}
A field is unexported if it starts with a lowercase letter:
type User struct {
name string
}
In your original code, the field is name, which is unexported. Because of that, json.Marshal cannot include it in the JSON output, so the result is:
{}
This rule exists because Go uses capitalization to control visibility across packages. The encoding/json package is a separate package, so it can only access exported fields.
To fix the problem, rename the field to Name:
type User struct {
Name string
}
Now json.Marshal can access it and will output:
{"Name":"Frank"}
If you want the JSON key to be lowercase, use a struct tag:
type User struct {
Name string `json:"name"`
}
That produces:
{"name":"Frank"}
This concept matters a lot in real Go programs because JSON is commonly used for:
- APIs
- configuration files
- request and response bodies
- storing structured data
- logging and messaging systems
If your fields are not exported correctly, your JSON may silently miss data.
Mental Model
Think of a Go struct as a building with many rooms.
- Exported fields are rooms with open doors and labels on them, so other packages can enter and read them.
- Unexported fields are rooms with locked doors, visible only inside the same package.
The encoding/json package is like a visitor from another building. It can only enter rooms with open doors.
So if your struct looks like this:
type User struct {
name string
}
json.Marshal walks up to the name field, finds the door locked, and skips it. With no readable fields left, it outputs:
{}
If you change it to:
type User struct {
Name string
}
now the door is open, and json.Marshal can include it in the JSON.
Syntax and Examples
Basic fix
Make the field exported by starting it with an uppercase letter.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
}
func main() {
user := &User{Name: "Frank"}
b, err := json.Marshal(user)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(b))
}
Output:
{"Name":"Frank"}
Using a JSON tag for lowercase keys
If you want the JSON field name to be name instead of Name, add a tag:
type User struct {
Name string `json:"name"`
}
Example:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
}
func main() {
user := User{Name: "Frank"}
b, _ := json.Marshal(user)
fmt.Println(string(b))
}
Output:
{"name":"Frank"}
Multiple fields example
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
When marshaled:
user := User{
Name: "Frank",
Email: "frank@example.com",
Age: 30,
}
Output:
{"name":"Frank","email":"frank@example.com","age":30}
Example with an unexported field still ignored
type User struct {
Name string `json:"name"`
password string `json:"password"`
}
Even though password has a JSON tag, it is still unexported, so it will not appear in the output.
That means this:
user := User{Name: "Frank", password: "secret"}
b, _ := json.Marshal(user)
fmt.Println(string(b))
prints:
{"name":"Frank"}
The tag does not override Go's export rules.
Step by Step Execution
Consider this working example:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
}
func main() {
user := User{Name: "Frank"}
b, err := json.Marshal(user)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
Step-by-step trace
1. Define the struct
type User struct {
Name string `json:"name"`
}
- The struct has one field:
Name - It starts with uppercase
N, so it is exported - The JSON tag says: use
nameas the key in JSON
2. Create a value
user := User{Name: "Frank"}
Now the user value contains:
Name = "Frank"
3. Marshal the struct
b, err := json.Marshal(user)
The encoding/json package:
- inspects the struct
- finds exported fields
- sees
Name - reads the tag
json:"name" - creates JSON with key
name - stores the result as bytes in
b
b now contains bytes representing:
{"name":"Frank"}
4. Check for errors
if err != nil {
fmt.Println(err)
return
}
- If marshaling fails, print the error
- In this case, there is no error
5. Print the JSON
fmt.Println(string(b))
bis a byte slice- converting it with
string(b)makes it readable text - output becomes:
{"name":"Frank"}
What happens in the broken version?
If the field is written like this:
type User struct {
name string
}
then during marshaling:
encoding/jsoninspects the struct- it sees
name nameis unexported- it skips the field
- no fields are included
- result becomes:
{}
Real World Use Cases
This concept appears constantly in Go development.
Building JSON APIs
When returning data from an HTTP handler, structs must use exported fields:
type Response struct {
Message string `json:"message"`
Success bool `json:"success"`
}
If fields are lowercase, clients may receive empty objects or missing data.
Parsing and generating request/response bodies
Go services often send and receive JSON between microservices. Exported fields ensure the payload contains the expected values.
Configuration files
Applications may load or save JSON config files using structs. If fields are unexported, config values may not be written correctly.
Database and API models
Developers often define structs that are reused for:
- database records
- API responses
- background jobs
- event payloads
Correct field exporting and JSON tags keep data consistent across systems.
Logging structured data
Some systems serialize structs into JSON logs. Exported fields determine what appears in the log output.
Real Codebase Usage
In real projects, developers usually combine exported fields with JSON tags.
Common pattern: exported field + lowercase JSON key
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
This keeps Go code idiomatic while matching common JSON naming conventions.
Validation before marshaling
Developers often validate struct values before returning them:
func buildUserResponse(name string) ([]byte, error) {
if name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
user := User{Name: name}
return json.Marshal(user)
}
This uses a guard clause to fail early.
Hiding internal fields
Real codebases often keep internal data out of JSON intentionally:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
password string
}
Here password stays internal because it is unexported.
DTO-style structs
Teams often create dedicated structs for API responses instead of exposing full internal models directly.
type UserResponse struct {
Name string `json:"name"`
}
This avoids accidentally exposing fields.
Error handling pattern
A standard Go pattern is:
b, err := json.Marshal(data)
if err != nil {
return err
}
Even though missing exported fields often do not cause an error, invalid values in some cases can.
Common Mistakes
1. Using lowercase field names
Broken code:
type User struct {
name string
}
Problem:
nameis unexportedjson.Marshalignores it
Fix:
type User struct {
Name string
}
2. Thinking JSON tags make unexported fields work
Broken code:
type User struct {
name string `json:"name"`
}
Problem:
- The tag does not make the field exported
- The field is still ignored
Fix:
type User struct {
Name string `json:"name"`
}
3. Forgetting to convert bytes to string when printing
Code:
b, _ := json.Marshal(user)
fmt.Println(b)
This prints a byte slice, not readable JSON.
Better:
fmt.Println(string(b))
4. Expecting pointers to change export behavior
Code like this still fails if fields are unexported:
user := &User{name: "Frank"}
Problem:
- Passing a pointer does not bypass export rules
5. Mixing Go naming and JSON naming
Beginners sometimes write this:
type User struct {
name string
user_email string
}
That is not idiomatic Go for exported JSON fields.
Better:
type User struct {
Name string `json:"name"`
UserEmail string `json:"user_email"`
}
6. Ignoring omitempty behavior later on
When learners add tags like this:
Name string `json:"name,omitempty"`
the field is omitted when empty. That can make it look like marshaling is broken when it is actually working as designed.
Comparisons
Exported vs unexported fields in Go
| Field name | Exported? | Accessible from other packages? | Included by json.Marshal? |
|---|---|---|---|
Name | Yes | Yes | Yes |
name | No | No | No |
Without tag vs with tag
| Struct field | JSON output |
|---|---|
Name string | {"Name":"Frank"} |
Name string \json:"name"`` | {"name":"Frank"} |
Pointer vs value when marshaling
| Input | Works? | Notes |
|---|---|---|
json.Marshal(user) where user is a struct value | Yes | Normal use |
json.Marshal(&user) where user is a struct | Yes | Also normal |
Pointers and values both work for marshaling. The important issue here is field export visibility, not whether the struct is passed by pointer.
Export rule vs JSON tag
| Feature | What it does | What it does not do |
|---|---|---|
Exported field (Name) | Makes field visible to other packages | Does not change JSON key name by itself |
JSON tag (json:"name") | Changes JSON key name | Does not make an unexported field visible |
Cheat Sheet
Quick rule
json.Marshal only includes exported struct fields.
Exported field
type User struct {
Name string
}
Unexported field
type User struct {
name string
}
This becomes:
{}
Lowercase JSON key with tag
type User struct {
Name string `json:"name"`
}
Output:
{"name":"Frank"}
Core syntax
b, err := json.Marshal(value)
if err != nil {
// handle error
}
fmt.Println(string(b))
Important rules
- Uppercase field name = exported = JSON can use it
- Lowercase field name = unexported = JSON ignores it
- JSON tags rename fields in output
- Tags do not override export rules
- Struct value and pointer both work with
json.Marshal
Common fix
From:
type User struct {
name string
}
To:
type User struct {
Name string `json:"name"`
}
FAQ
Why does Go json.Marshal return {} for my struct?
Because your struct fields are unexported, usually due to starting with lowercase letters. encoding/json only marshals exported fields.
Do I need to use uppercase field names in Go structs for JSON?
Yes, if you want encoding/json to access those fields. Use uppercase Go field names and JSON tags if you want lowercase JSON keys.
Can I use a JSON tag on a lowercase field in Go?
You can write the tag, but it will not help. Unexported fields are still ignored.
How do I make the JSON output use name instead of Name?
Use a tag:
Name string `json:"name"`
Does json.Marshal work differently for pointers and struct values?
No. Both pointers and values work. The issue is field visibility, not pointer usage.
Why doesn't Go return an error when fields are skipped?
Skipping unexported fields is expected behavior, so encoding/json simply ignores them instead of treating them as an error.
How can I hide a field from JSON on purpose?
Use an unexported field or use a tag like:
Password string `json:"-"`
That tells the JSON encoder not to include it.
Mini Project
Description
Create a small Go program that converts user data into JSON for an API-style response. This demonstrates the exact rule behind the original problem: exported struct fields are included in JSON, and JSON tags control the final key names.
Goal
Build a program that marshals a User struct into readable JSON with lowercase JSON field names.
Requirements
- Define a
Userstruct with at least three exported fields. - Use JSON struct tags so the output keys are lowercase.
- Create a user value with sample data.
- Convert the struct to JSON using
json.Marshal. - Print the JSON output as a string.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.