Question
I retrieve an item from MongoDB using this Go struct:
type Page struct {
PageId string `bson:"pageId"`
Meta map[string]interface{} `bson:"meta"`
}
I also need to encode this value as JSON. Without JSON tags, the output uses exported Go field names such as PageId instead of pageId.
How can I define both BSON and JSON field-name tags on the same struct field? I tried separating them with a comma, but it did not work:
type Page struct {
PageId string `bson:"pageId",json:"pageId"`
Meta map[string]interface{} `bson:"meta",json:"pageId"`
}
Short Answer
Go struct tags can contain metadata for more than one package. You will learn the correct syntax for combining bson and json tags, how tag options work, and how to avoid common field-name mistakes when moving data between MongoDB and JSON APIs.
Concept
A struct tag is text attached to a Go struct field. Packages use that text as metadata when converting a struct to or from another format.
For example:
- MongoDB BSON libraries commonly read the
bsontag. - Go's standard
encoding/jsonpackage reads thejsontag.
A single field can have multiple tags because each tag has its own key. Tags must be written inside one backtick-delimited string, with tag entries separated by spaces:
`bson:"pageId" json:"pageId"`
A comma does not separate tag keys. Instead, commas belong inside the value of one tag to specify options such as omitempty:
`json:"pageId,omitempty"`
This matters because Go field names must be exported (start with an uppercase letter) for encoders to access them, while database documents and API payloads often use lower camel case, snake case, or another external naming convention.
Mental Model
Think of a struct field as a person who has different name badges for different places.
- In Go code, the field is named
PageIdso it is exported and accessible. - At MongoDB's entrance, it wears the
bson:"pageId"badge. - At a JSON API's entrance, it wears the
json:"pageId"badge.
Both badges are attached to the same field, but each system reads only the badge intended for it. Put the badges side by side with a space, not a comma.
Syntax and Examples
The general syntax is:
type TypeName struct {
FieldName FieldType `packageName:"externalName" anotherPackage:"externalName"`
}
For BSON and JSON, write one tag string and separate the tag entries with spaces:
package main
import (
"encoding/json"
"fmt"
)
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
Meta map[string]interface{} `bson:"meta" json:"meta"`
}
func main() {
page := Page{
PageID: "home",
Meta: map[string]interface{}{
"title": "Welcome",
},
}
data, err := json.Marshal(page)
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
Output:
{"pageId":"home","meta":{"title":"Welcome"}}
PageID is a common Go spelling for an initialism such as ID, but PageId also works. The tag controls the external name in either case.
The Meta field must use json:"meta", not json:"pageId". Each field should normally have its own appropriate external name.
Step by Step Execution
Consider this code:
page := Page{
PageID: "about",
Meta: map[string]interface{}{
"published": true,
},
}
data, err := json.Marshal(page)
When json.Marshal runs:
- It examines the exported
PageIDfield. - It finds
json:"pageId"in that field's tag. - It writes the JSON key as
"pageId", not"PageID". - It reads the value
"about"and writes it as the value of that key. - It examines
Metaand findsjson:"meta". - It converts the map to a JSON object.
datacontains JSON equivalent to:
{"pageId":"about","meta":{"published":true}}
If the program instead used a MongoDB BSON encoder or decoder, that package would look for bson:"pageId" and bson:"meta" on the same fields.
Real World Use Cases
- MongoDB-backed REST APIs: Read a document with BSON names and send a JSON response using the same or different API names.
- Data migration tools: Decode old documents using legacy BSON field names while producing a new JSON export format.
- Configuration services: Store settings in MongoDB and expose selected settings through JSON endpoints.
- Event publishing: Read application data from a database, then serialize it as JSON for a queue, webhook, or log stream.
- Third-party integrations: Keep idiomatic exported Go field names while matching another service's exact JSON naming rules.
Real Codebase Usage
In production code, structs often serve one boundary only: a database model, an API request, or an API response. If one struct is used at multiple boundaries, multiple tags are common.
Typical shared model
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
Meta map[string]interface{} `bson:"meta" json:"meta"`
}
Omitting empty optional fields
Use omitempty as an option within the relevant tag value:
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
Meta map[string]interface{} `bson:"meta,omitempty" json:"meta,omitempty"`
}
Hiding an internal field from JSON
A field can have different behavior for each encoder:
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
InternalNotes string `bson:"internalNotes" json:"-"`
}
json:"-" means the field is never included in JSON output, while the BSON library can still store it.
For larger applications, developers often use separate database and API structs when their shapes or security rules differ. This prevents internal fields from being accidentally returned in an HTTP response.
Common Mistakes
Separating tag keys with a comma
This is invalid struct-tag syntax for two keys:
type Page struct {
PageID string `bson:"pageId",json:"pageId"`
}
Use a space between tag entries:
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
}
Giving two fields the same JSON name by accident
The attempted Meta tag used json:"pageId". That creates duplicate JSON keys or ambiguous behavior.
// Incorrect
type Page struct {
PageID string `json:"pageId"`
Meta map[string]interface{} `json:"pageId"`
}
Use json:"meta" for Meta.
Using unexported fields
The standard JSON package ignores unexported fields:
type Page struct {
pageID string `json:"pageId"` // ignored by encoding/json
}
Export the field by starting its Go name with an uppercase letter:
type Page struct {
PageID string `json:"pageId"`
}
Putting omitempty outside the tag value
Incorrect:
PageID string `json:"pageId",omitempty`
Correct:
PageID string `json:"pageId,omitempty"`
Expecting tags to rename the Go field
Tags only affect encoders and decoders that understand them. In Go code, you still access the field as page.PageID, not page.pageId.
Comparisons
| Situation | Syntax | Meaning |
|---|---|---|
| One JSON name | `json:"pageId"` | JSON uses pageId. |
| BSON and JSON names | `bson:"pageId" json:"pageId"` | Each encoder uses its own tag. |
| JSON field optional | `json:"pageId,omitempty"` | Omit the field when it has an empty value. |
| Exclude from JSON | `json:"-"` | Never serialize or deserialize this field as JSON. |
| Different external names | `bson:"page_id" json:"pageId"` | MongoDB and JSON can use different conventions. |
A comma has two different roles:
| Location | Role |
|---|---|
| Between tag entries | Do not use a comma; use a space. |
| Inside one tag value | Use a comma for options, such as json:"meta,omitempty". |
Cheat Sheet
// Multiple tag keys: separate with spaces.
type Page struct {
PageID string `bson:"pageId" json:"pageId"`
Meta map[string]interface{} `bson:"meta" json:"meta"`
}
// Options belong after the external name, inside one tag value.
type Page struct {
Meta map[string]interface{} `bson:"meta,omitempty" json:"meta,omitempty"`
}
// Exclude from one format.
type Page struct {
Secret string `bson:"secret" json:"-"`
}
Rules:
- Wrap the entire tag in backticks.
- Separate tag keys with spaces.
- Use commas only for options such as
omitempty. - Use exported Go fields for
encoding/json. - Ensure each field has the intended JSON and BSON name.
- Tags affect serialization libraries, not how fields are accessed in Go code.
FAQ
Can a Go struct field have multiple tags?
Yes. Put multiple key:"value" entries inside one backtick-delimited struct tag, separated by spaces.
Why does encoding/json output PageId by default?
Without a json tag, encoding/json uses the exported Go field name as the JSON key.
Should I use PageId or PageID in Go?
Both compile, but PageID follows the common Go convention for initialisms. The JSON or BSON tag can still be pageId.
Can BSON and JSON use different field names?
Yes:
PageID string `bson:"page_id" json:"pageId"`
What does omitempty do?
It omits a field during encoding when the value is empty, such as "", 0, false, nil, or an empty slice/map.
Does json:"-" prevent MongoDB from storing the field?
No. It only affects JSON. Add bson:"-" too if you also want the BSON encoder to ignore it.
Are struct tags checked by the Go compiler?
The compiler accepts tags as strings. Whether they are meaningful depends on the package reading them. Tools such as go vet can detect malformed tags.
Mini Project
Description
Build a small Go program that represents a page retrieved from MongoDB and serializes it as an API-friendly JSON response. It demonstrates using BSON and JSON tags on the same struct, including an optional field that is not exposed publicly.
Goal
Create a Page value and print JSON with lower camel-case field names while retaining MongoDB BSON mappings.
Requirements
Requirement 1
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.