383 lines
7.5 KiB
Go
383 lines
7.5 KiB
Go
package commons
|
|
|
|
import (
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
"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
|
|
}
|
|
|
|
// StructMustTomap given any struct return the equivalent map[string]any or nil.
|
|
// Will never throw.
|
|
func StructMustToMap(data any) map[string]any {
|
|
b, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Printf("Failed marshal %v", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
var res map[string]any
|
|
err = json.Unmarshal(b, &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 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 BsonToMap(b bson.M) map[string]any {
|
|
result := make(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
|
|
}
|
|
|
|
// 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
|
|
} |