Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildMongoUpdateMap ¶
BuildMongoUpdateMap constructs a map[string]any for use in MongoDB updates.
It iterates over the fields of the input struct (or pointer to struct), extracts non-nil values, and builds a map where keys correspond to BSON field names. Unexported fields and fields with nil pointer values are skipped.
Keys are determined by examining struct tags in priority: bson:"..." -> json:"..." -> form:"..." -> fallback to lower-casing the first letter of the field name. Tags with a value of "-" will cause the field to be skipped.
Fields are skipped entirely if their derived key appears in the `skipFields` list. Note: This function processes all top-level fields (including embedded structs) individually. It does not explicitly handle or flatten embedded structs; embedded struct values (whether named or anonymous) will be included as nested Go struct values in the map, which MongoDB drivers typically serialize as nested BSON documents.
Parameters:
x: The struct or pointer to struct to traverse. Must be a struct or pointer to a non-nil struct. skipFields: A list of strings representing BSON field names (derived from tags or naming) to exclude from the result map. These apply to top-level keys generated by this function.
Returns:
A map[string]any containing the extracted fields and their values, intended for MongoDB updates. If the input is not a valid struct or pointer, an empty map is returned and a warning is printed.
Example:
type Address struct {
Street string `bson:"street_addr"` // bson tag (highest within Address)
City string `json:"city_name"` // json tag (next within Address)
Zip string `form:"zip_code"` // form tag (fallback within Address)
Code string // No tags, uses firstCharToLower -> "code" within Address
}
type User struct {
ID string `bson:"_id"` // bson tag (highest priority)
FirstName string `json:"first"` // json tag (next priority)
LastName string `form:"last"` // form tag (fallback priority)
Age int // No tags, uses firstCharToLower -> "age"
Status *string `bson:"status"` // Pointer field with bson tag
Address Address // Named embedded struct, treated as regular field
Profile struct { // Anonymous embedded struct, treated as regular field
Public bool `bson:"is_public"` // bson tag within Profile
} // No tags on the anonymous field itself, uses firstCharToLower -> "profile"
}
status := "active"
user := User{
ID: "abc",
FirstName: "Jane",
LastName: "Doe",
Age: 30,
Status: &status, // Non-nil pointer
Address: Address{Street: "Main St", City: "Anytown", Zip: "12345", Code: "ABC"},
Profile: struct { Public bool `bson:"is_public"`}{Public: true},
}
// Call BuildMongoUpdateMap(user, []string{"_id", "Age", "address", "profile"}) // skip top-level keys
// Expected map keys and values:
// {
// "first": "Jane", // json:"first" (no bson)
// "last": "Doe", // form:"last" (no bson, no json)
// "status": "active", // bson:"status" (has bson, pointer dereferenced)
// "Address": Address{...}, // No tags on Address field, firstCharToLower("Address") -> "address". Value is the Struct.
// // "profile": Profile{...}, // Skipped as "profile" is in skipFields.
// }
// Note: "_id" and "Age" are skipped. "address" is not skipped. "profile" is skipped.
// The MongoDB driver would serialize the Address struct value {Street:"...", City:"...", Zip:"...", Code:"..."}
// into a nested BSON document under the key "address".
// Its internal fields (Street, City, Zip, Code) would get keys based on *their* tags/fallbacks
// ("street_addr", "city_name", "zip_code", "code").
// The Profile struct value {Public: true} would be nested under the key "profile",
// and "Public" would get key "is_public" from its bson tag.
func BuildRDBUpdateMap ¶
BuildRDBUpdateMap constructs a map[string]any for use in relational database updates.
It iterates over the fields of the input struct (or pointer to struct), extracts non-nil values, and builds a map suitable for database update operations. Unexported fields and fields with nil pointer values are skipped.
The keys in the resulting map are determined by examining struct tags in a specific conditional priority order for each field:
1. gorm:"column:..." tag: The value specified in the 'column' option (e.g., `gorm:"column:my_col"`). 2. bson:"..." tag: The value before the first comma (e.g., `bson:"mongo_field,omitempty"`). 3. Conditional json:"..." tag (JSONB priority): If the field's gorm tag indicates it's a JSONB type (contains "type:jsonb" or "jsonb"), the json tag's value (e.g., `json:"api_field"`) is checked here. If a valid key is found, it's used. Otherwise (json tag missing or "-"), the process falls through to the default naming fallback. 4. Default Naming Fallback: If no previous tags (gorm:column, bson, and conditional json for JSONB) provided a valid key, the field name is converted using the package-level default naming function (see SetDefaultColumnNameFunc). By default, this is snake_case (see DefaultSnakeCaseNamer). 5. Final json:"..." tag (Non-JSONB fallback): If the field is NOT a JSONB type AND the default naming fallback (step 4) resulted in an empty key (which should only happen if the DefaultColumnNameFunc returns an empty string), the json tag's value is checked as a final option.
Fields are skipped entirely if their derived map key is an empty string (e.g., a tag was set to "-") or if the derived key appears in the `skipFields` list.
Anonymous embedded structs marked with `gorm:"embedded"` are recursively processed, and their internal fields are flattened into the top-level map according to the same key determination priority rules. This matches the standard GORM behavior for embedding in RDBs. Named embedded structs are treated as regular fields; the entire struct value will be included in the map if the field is not skipped (usually not suitable for RDB updates unless the target column is a compatible type like JSONB).
Fields marked with `gorm:"type:jsonb"` or containing "jsonb" in their gorm tag are treated as JSONB columns. Their value is marshaled into a JSON string (`json.RawMessage`) before being added to the map.
Parameters:
x: The struct or pointer to struct to traverse. Must be a struct or pointer to a non-nil struct. skipFields: A list of strings representing map keys (database column names derived from tags or naming) to exclude from the result map.
Returns:
A map[string]any containing the extracted fields and their values, intended for database updates. An error if the input is not a valid struct or pointer, or if JSON marshaling of a JSONB field fails.
Example:
type Address struct {
Street string `json:"street"` // json tag
City string // No tags, relies on default namer
}
type User struct {
ID uint `gorm:"column:user_id"` // gorm column tag (highest priority)
FirstName string `json:"firstName"` // json tag (lower prio for non-jsonb)
LastName string // No tags, relies on default namer
address Address `gorm:"embedded"` // Anonymous embedded struct for flattening
Settings map[string]string `gorm:"type:jsonb" json:"user_settings"` // JSONB + json tag
}
// Assume SetDefaultColumnNameFunc is set to DefaultSnakeCaseNamer in your init code.
user := User{
ID: 1,
FirstName: "Jane",
LastName: "Doe",
address: Address{Street: "Main St", City: "Anytown"},
Settings: map[string]string{"theme": "dark"},
}
updateMap, err := BuildRDBUpdateMap(user, []string{"user_id"})
// updateMap will be (assuming DefaultSnakeCaseNamer):
// {
// "first_name": "Jane", // Non-JSONB: defaultNamer("FirstName") -> "first_name" (wins over json:"firstName" for non-JSONB)
// "last_name": "Doe", // Non-JSONB: defaultNamer("LastName") -> "last_name"
// "street": "Main St", // Flattened, Non-JSONB: defaultNamer("Street") -> "street" (wins over json:"street")
// "city": "Anytown", // Flattened, Non-JSONB: defaultNamer("City") -> "city"
// "user_settings": json.RawMessage(`{"theme":"dark"}`), // JSONB: json:"user_settings" -> "user_settings" (wins over defaultNamer for JSONB)
// }
// Note: "user_id" is skipped because it's in skipFields.
func DefaultSnakeCaseNamer ¶
DefaultSnakeCaseNamer is a DefaultColumnNameFunc that converts field names to snake_case. This serves as the built-in fallback if no custom NamingStrategy function is provided via SetDefaultColumnNameFunc or if SetDefaultColumnNameFunc(nil) is called.
func SetDefaultColumnNameFunc ¶
func SetDefaultColumnNameFunc(namer DefaultColumnNameFunc)
SetDefaultColumnNameFunc sets the package-level default column name function. This function should typically be called once during application initialization. If nil is passed, the default namer will be reset to DefaultSnakeCaseNamer. Note: If called concurrently with BuildRDBUpdateMapV6 after initialization, this could theoretically lead to race conditions, but this is an uncommon use case.
Types ¶
type DefaultColumnNameFunc ¶
DefaultColumnNameFunc is the type for a function that provides a default column name for a struct field based on its Go name, used when no tags specify the name. Users can provide a custom function matching their GORM NamingStrategy.