Files
commons/struct.go
George Suntres 28dcfb9e4c Add bson clone
2026-04-23 15:37:20 -04:00

546 lines
10 KiB
Go

package commons
import (
"log"
"reflect"
"strings"
"bytes"
"encoding/json"
"github.com/go-viper/mapstructure/v2"
"go.mongodb.org/mongo-driver/v2/bson"
)
// StructHasStringValue given any struct will check if the requested property has
// a non-blank value.
func StructHasStringValue(o any, field string) bool {
v := reflect.ValueOf(o).Elem().FieldByName(field)
return v.String() != ""
}
// StructHasNotStringValue given any struct will check if the requested property has
// a blank value.
func StructHasNotStringValue(o any, field string) bool {
return !StructHasStringValue(o, field)
}
// StructHasProperty cheks if a struct has the requested property.
func StructHasProperty(value interface{}, name string) bool {
vo := reflect.ValueOf(value).Elem()
typeOfValue := vo.Type()
has := false
for i:= 0; i < vo.NumField(); i++ {
if typeOfValue.Field(i).Name == name {
has = true
break
}
}
return has
}
func MapOmit[K comparable, V any](m map[K]V, keys ...K) map[K]V {
out := make(map[K]V, len(m))
omit := make(map[K]struct{}, len(keys))
for _, k := range keys {
omit[k] = struct{}{}
}
for k, v := range m {
if _, ok := omit[k]; !ok {
out[k] = v
}
}
return out
}
// MapMerge merges maps into one.
func MapMerge[T ~map[string]any](maps...T) T {
out := make(T)
for _, m := range maps {
if m != nil {
for k, v := range m {
out[k] = v
}
}
}
return out
}
// StructMustTomap given any struct return the equivalent map[string]any or nil.
// Will never throw. Will also work for map[string]any.
func StructMustToMap(data any) map[string]any {
b, err := json.Marshal(data)
if err != nil {
log.Printf("Failed marshal %v", err)
return nil
}
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
var res map[string]any
err = dec.Decode(&res)
if err != nil {
log.Printf("Failed to unmarshal %v", err)
return nil
}
return res
}
// StructSetValue will update the value of the given field of struct o.
func StructSetValue(o any, field string, value any) {
ref := reflect.ValueOf(o).Elem()
if ref.Kind() == reflect.Ptr {
ref = reflect.Indirect(ref)
}
if ref.Kind() == reflect.Interface {
ref = ref.Elem()
}
if ref.Kind() == reflect.Struct {
f := ref.FieldByName(field)
if f.IsValid() && f.CanSet() {
f.Set(reflect.ValueOf(value))
}
}
}
func StructToStruct(source any, target any) {
mapstructure.Decode(source, &target)
}
// StructToMapRecursive given a struct or a primitive will return the equivalent
// map[string]any of the struct or the primitive as is.
func StructToMapRecursive(obj any) any {
v := reflect.ValueOf(obj)
// Handle pointers by getting the underlying element
if v.Kind() == reflect.Ptr {
if v.IsNil() { return nil }
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
result := make(map[string]any)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
// Skip unexported fields (private fields)
if field.PkgPath != "" { continue }
// Recurse into the field's value
result[field.Name] = StructToMapRecursive(v.Field(i).Interface())
}
return result
case reflect.Slice, reflect.Array:
result := make([]any, v.Len())
for i := 0; i < v.Len(); i++ {
result[i] = StructToMapRecursive(v.Index(i).Interface())
}
return result
default:
// Return basic types (int, string, etc.) as is
return obj
}
}
func BsonClone(m map[string]any) map[string]any {
if m == nil {
return nil
}
out := make(map[string]any, len(m))
for k, v := range m {
out[k] = deepCopy(v)
}
return out
}
func deepCopy(v any) any {
switch val := v.(type) {
case bson.M:
return BsonClone(map[string]any(val))
case bson.A:
arr := make([]any, len(val))
for i, item := range val {
arr[i] = deepCopy(item)
}
return arr
case bson.D:
d := make(bson.D, len(val))
for i, elem := range val {
d[i] = bson.E{
Key: elem.Key,
Value: deepCopy(elem.Value),
}
}
return d
case map[string]any: // bson.M
return BsonClone(val)
case []any: // bson.A
arr := make([]any, len(val))
for i, item := range val {
arr[i] = deepCopy(item)
}
return arr
default:
// primitives (string, int, bool, etc.)
return val
}
}
func BsonDGetAny(d bson.D, key string) (any, bool) {
for _, e := range d {
if e.Key == key {
return e.Value, true
}
}
return nil, false
}
func BsonDGet[T any](d bson.D, key string) (T, bool) {
var zero T
for _, e := range d {
if e.Key == key {
val, ok := e.Value.(T)
if !ok {
return zero, false
}
return val, true
}
}
return zero, false
}
func BsonDGetString(d bson.D, key string) (string, bool) {
return BsonDGet[string](d, key)
}
func BsonToStruct(m bson.M, o any) error {
b, err := bson.Marshal(m)
if err != nil {
log.Printf("Failed marshal %v", err)
return err
}
err = bson.Unmarshal(b, o)
if err != nil {
log.Printf("Failed to unmarshal %v", err)
return err
}
return nil
}
// MapToStruct will convert a map[string]any to a struct.
func MapToStruct(m map[string]any, o any) error {
b, err := bson.Marshal(m)
if err != nil {
log.Printf("Failed marshal %v", err)
return err
}
err = bson.Unmarshal(b, o)
if err != nil {
log.Printf("Failed to unmarshal %v", err)
return err
}
return nil
}
func BsonAnyToMap(v any) any {
switch v.(type) {
case bson.D:
m := map[string]any{}
val := v.(bson.D)
for _, elem := range val {
m[elem.Key] = BsonAnyToMap(elem.Value)
}
return m
case bson.M:
m := map[string]any{}
val := v.(bson.M)
for k, v2 := range val {
m[k] = BsonAnyToMap(v2)
}
return m
case map[string]any:
m := map[string]any{}
val := v.(map[string]any)
for i, v2 := range val {
m[i] = BsonAnyToMap(v2)
}
return m
case bson.A:
val := v.(bson.A)
arr := make([]any, len(val))
for i, v2 := range val {
arr[i] = BsonAnyToMap(v2)
}
return arr
default:
return v
}
return v
}
func BsonToMap(b bson.M) map[string]any {
result := map[string]any{}
for k, v := range b {
switch val := v.(type) {
case bson.M:
result[k] = BsonToMap(val)
case []interface{}:
arr := make([]any, len(val))
for i, item := range val {
if nested, ok := item.(bson.M); ok {
arr[i] = BsonToMap(nested)
} else {
arr[i] = item
}
}
result[k] = arr
default:
result[k] = val
}
}
return result
}
type WrapA struct {
Items []map[string]any
}
// BsonAToSlice will convert a bson.A to []map[string]any.
func BsonAToSlice(m any) ([]map[string]any, error) {
v := bson.M{
"items": m.(bson.A),
}
b, err := bson.Marshal(v)
if err != nil {
log.Printf("Failed marshal %v", err)
return nil, err
}
var o WrapA
err = bson.Unmarshal(b, &o)
if err != nil {
log.Printf("Failed to unmarshal %v", err)
return nil, err
}
return o.Items, nil
}
func MapFromString(s string) (map[string]any, error) {
var b map[string]any
if err := bson.Unmarshal([]byte(s), &b); err != nil {
return nil, err
}
return b, nil
}
// MapIsSubset given two map[string]any m1 and m2 will determine if m1 is a subset of m2.
// Only fields' name is evaluated not their values.
func MapIsSubset(subset, superset any) bool {
sub := subset.(map[string]any)
sup := superset.(map[string]any)
isSubset := true
for k, _ := range sub {
_, ok := sup[k]
if !ok {
isSubset = false
}
}
return isSubset
}
// MapIsSubsetOfStruct given a map[string]any and a struct will determine if the map is a subset of the struct.
// Only fields' name is evaluated not their values.
func MapIsSubsetOfStruct(m map[string]any, s any) bool {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem() // Dereference if it's a pointer
}
// Ensure we are working with a struct
if v.Kind() != reflect.Struct {
return false
}
isSubset := true
t := v.Type()
for key := range m {
// FieldByName only finds exported fields
if _, found := t.FieldByName(key); !found {
isSubset = false
break
}
}
return isSubset
}
// StructHasJsonName determines if a struct has the given json tag name.
func StructHasJsonName(s any, targetName string) bool {
t := reflect.TypeOf(s)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("json")
// The name is the first part before any commas (e.g., "user_id,omitempty")
name := strings.Split(tag, ",")[0]
if name == targetName {
return true
}
}
return false
}
// StructHasJsonName determines if a struct has the given bson tag name.
func StructHasBsonName(s any, targetName string) bool {
t := reflect.TypeOf(s)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("bson")
// The name is the first part before any commas (e.g., "user_id,omitempty")
name := strings.Split(tag, ",")[0]
if name == targetName {
return true
}
}
return false
}
// StructCopyMatching copies the fields of one struct to another only if they have the same name and type.
func StructCopyMatching(source, target any) {
sVal := reflect.ValueOf(source).Elem()
tVal := reflect.ValueOf(target).Elem()
for i := 0; i < sVal.NumField(); i++ {
sField := sVal.Type().Field(i)
tField, ok := tVal.Type().FieldByName(sField.Name)
if ok && sField.Type == tField.Type {
tVal.FieldByName(sField.Name).Set(sVal.Field(i))
}
}
}
// GetWithDefault
func GetWithDefault(m map[string]any, path string, def any) any {
keys := strings.Split(path, ".")
var current any = m
for _, k := range keys {
if m2, ok := current.(map[string]any); ok {
if val, exists := m2[k]; exists {
current = val
} else {
return def
}
} else {
return def
}
}
return current
}
func GetStringWithDefault(m map[string]any, path, def string) string {
a := GetWithDefault(m, path, def)
return a.(string)
}
func GetString(m map[string]any, path string) string {
a := Get(m, path)
if a == nil {
return ""
} else {
return a.(string)
}
}
func Get(m map[string]any, path string) any {
keys := strings.Split(path, ".")
var current any = m
for _, k := range keys {
if m2, ok := current.(map[string]any); ok {
if val, exists := m2[k]; exists {
current = val
} else {
return nil
}
} else {
return nil
}
}
return current
}