Skip to content
鼓励作者:欢迎打赏犒劳

Map工具类

map获取key的val

  • 如果 key 不存在于 map 中,返回默认值
  • 如果 key 存在,但对应的 值是 零值 返回默认值
go
// Package maputil 提供从 map[string]interface{} 安全提取各种类型值的工具函数。
package maputil

import (
	"math"
	"strconv"
	"strings"
)

// GetStringFromMap 从 map 中获取 string 值。
// 如果 key 不存在或类型不匹配,返回默认值(如果提供),否则返回空字符串。
func GetStringFromMap(m map[string]interface{}, key string, defaultValue ...string) string {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return ""
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return ""
	}
	if str, ok := v.(string); ok {
		return str
	}
	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return ""
}

// GetBoolFromMap 从 map 中获取 bool 值。
// 支持 bool、string("true"/"false")、整数(非零为 true)等类型。
func GetBoolFromMap(m map[string]interface{}, key string, defaultValue ...bool) bool {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return false
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return false
	}

	switch val := v.(type) {
	case bool:
		return val
	case string:
		lower := strings.ToLower(strings.TrimSpace(val))
		if lower == "true" {
			return true
		} else if lower == "false" {
			return false
		}
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		return val != 0
	case float32, float64:
		return val != 0
	}

	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return false
}

// GetInt32FromMap 从 map 中获取 int32 值。
func GetInt32FromMap(m map[string]interface{}, key string, defaultValue ...int32) int32 {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0
	}

	switch val := v.(type) {
	case int32:
		return val
	case int:
		if val >= math.MinInt32 && val <= math.MaxInt32 {
			return int32(val)
		}
	case int64:
		if val >= math.MinInt32 && val <= math.MaxInt32 {
			return int32(val)
		}
	case float64:
		if val == float64(int32(val)) && val >= math.MinInt32 && val <= math.MaxInt32 {
			return int32(val)
		}
	case string:
		if i, err := strconv.ParseInt(val, 10, 32); err == nil {
			return int32(i)
		}
	}

	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return 0
}

func GetInt64FromMap(m map[string]interface{}, key string, defaultValue ...int64) int64 {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0
	}

	switch val := v.(type) {
	case int64:
		return val
	case int:
		return int64(val)
	case int32:
		return int64(val)
	case int16:
		return int64(val)
	case int8:
		return int64(val)
	case uint:
		if val <= math.MaxInt64 {
			return int64(val)
		}
	case uint64:
		if val <= math.MaxInt64 {
			return int64(val)
		}
	case float64:
		if val == float64(int64(val)) && val >= math.MinInt64 && val <= math.MaxInt64 {
			return int64(val)
		}
	case string:
		if i, err := strconv.ParseInt(val, 10, 64); err == nil {
			return i
		}
	default:
		// 无法识别的类型,走默认值或零值
	}

	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return 0
}

// GetFloat64FromMap 从 map 中获取 float64 值。
// GetFloat64FromMap 从 map 中安全获取 float64 值。
// 支持 float64、float32、各类整数、字符串(可解析为浮点数)。
func GetFloat64FromMap(m map[string]interface{}, key string, defaultValue ...float64) float64 {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0.0
	}

	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return 0.0
	}

	switch val := v.(type) {
	case float64:
		return val
	case float32:
		return float64(val)
	case int:
		return float64(val)
	case int8:
		return float64(val)
	case int16:
		return float64(val)
	case int32:
		return float64(val)
	case int64:
		return float64(val)
	case uint:
		return float64(val)
	case uint8:
		return float64(val)
	case uint16:
		return float64(val)
	case uint32:
		return float64(val)
	case uint64:
		return float64(val)
	case string:
		if f, err := strconv.ParseFloat(val, 64); err == nil {
			return f
		}
		// 字符串无法解析,视为无效
	default:
		// 不支持的类型(如 bool、map、slice 等)
	}

	// 类型不匹配或解析失败,返回默认值或零值
	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return 0.0
}

// GetStringSliceFromMap 从 map 中获取 []string。
// 支持 []string 和 []interface{}(常见于 JSON)。
func GetStringSliceFromMap(m map[string]interface{}, key string, defaultValue ...[]string) []string {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return nil
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return nil
	}

	if slice, ok := v.([]string); ok {
		return slice
	}

	if ifaceSlice, ok := v.([]interface{}); ok {
		result := make([]string, len(ifaceSlice))
		for i, item := range ifaceSlice {
			if str, ok := item.(string); ok {
				result[i] = str
			} else {
				// 元素非 string,整个视为无效
				if len(defaultValue) > 0 {
					return defaultValue[0]
				}
				return nil
			}
		}
		return result
	}

	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return nil
}

// GetMapFromMap 从 map 中获取嵌套的 map[string]interface{}。
func GetMapFromMap(m map[string]interface{}, key string, defaultValue ...map[string]interface{}) map[string]interface{} {
	if m == nil {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return nil
	}
	v, exists := m[key]
	if !exists {
		if len(defaultValue) > 0 {
			return defaultValue[0]
		}
		return nil
	}
	if nested, ok := v.(map[string]interface{}); ok {
		return nested
	}
	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return nil
}

测试

go
package main

import (
	"demo/utils/maputil"
	"fmt"
)

func main() {
	data := map[string]interface{}{
		"name":   "Alice",
		"age":    "25",
		"active": "true",
		"tags":   []interface{}{"go", "golang"},
		"config": map[string]interface{}{"debug": true},
	}

	name := maputil.GetStringFromMap(data, "name")
	age := maputil.GetInt64FromMap(data, "age")
	active := maputil.GetBoolFromMap(data, "active")
	tags := maputil.GetStringSliceFromMap(data, "tags")
	config := maputil.GetMapFromMap(data, "config")

	// 带默认值
	email := maputil.GetStringFromMap(data, "email", "unknown@example.com")
	retry := maputil.GetInt32FromMap(data, "retry", 3)

	// 打印结果
	fmt.Println("name:", name)
	fmt.Println("age:", age)
	fmt.Println("active:", active)
	fmt.Println("tags:", tags)
	fmt.Println("config:", config)
	fmt.Println("email (with default):", email)
	fmt.Println("retry (with default):", retry)
}

StructToMap

会跳过零值

注意,即便是字段是指针类型,val也是值,所以不用担心

go
package bean_cover_util

import (
	"reflect"
	"strings"
)

// resolveValue 解引用指针,直到非指针类型。
// 如果遇到 nil 指针,返回一个 IsValid() == false 的 Value。
func resolveValue(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return reflect.Value{} // invalid value
		}
		v = v.Elem()
	}
	return v
}

// isEmptyValue 判断一个 reflect.Value 是否为空值(zero value)
func isEmptyValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.String:
		return v.String() == ""
	case reflect.Map, reflect.Slice, reflect.Array:
		return v.Len() == 0
	case reflect.Struct:
		// struct 只有所有字段都为空才算空?通常不认为 struct 是“空”,除非你有特殊逻辑
		// 这里我们保守处理:struct 不算空(因为可能包含有效嵌套数据)
		return false
	case reflect.Interface, reflect.Ptr:
		// 已经被 resolveValue 处理过,理论上不会到这里
		return v.IsNil()
	default:
		// chan, func 等,视为非空或不支持
		return false
	}
}

// StructToMap 将结构体转换为 map[string]interface{}。
//
// 参数:
//   - v: 要转换的结构体(值或指针)
//   - omitEmpty (可选): 是否忽略空值字段(zero value),默认 false(保留所有字段)
//
// 支持:
//   - 指针或值类型的 struct
//   - json tag 解析(忽略 `,omitempty` 等修饰)
//   - 嵌套 struct 转 map
//   - struct slice 转 []map[string]interface{}
//   - 指针字段自动解引用(如 *string, *User)
//   - 可选忽略空值字段(通过 omitEmpty 参数)
//
// 规则:
//   - 忽略 json:"-" 或无 json tag 的字段(⚠️ 注意:这里“无 tag”仍会使用字段名,见下)
//     实际上:无 json tag 时使用小驼峰字段名,不会被忽略
//   - 若 tag 包含 ,(如 "name,omitempty"),仅取前面部分
//   - 若 tag 为空,则使用小驼峰字段名(fieldName)
//   - 非导出字段(小写开头)不会被反射访问
func StructToMap(v interface{}, omitEmpty ...bool) map[string]interface{} {
	doOmit := len(omitEmpty) > 0 && omitEmpty[0]

	m := make(map[string]interface{})
	rv := reflect.ValueOf(v)

	// 解引用顶层指针
	if rv.Kind() == reflect.Ptr {
		if rv.IsNil() {
			return m
		}
		rv = rv.Elem()
	}

	if rv.Kind() != reflect.Struct {
		return m
	}

	rt := rv.Type()
	for i := 0; i < rv.NumField(); i++ {
		field := rt.Field(i)
		value := rv.Field(i)

		// 获取 json tag
		tag := field.Tag.Get("json")
		if tag == "-" {
			continue
		}

		// 去除 tag 中的选项(如 ",omitempty")
		if idx := strings.Index(tag, ","); idx != -1 {
			tag = tag[:idx]
		}

		key := tag
		if key == "" {
			// 使用小驼峰命名
			key = strings.ToLower(field.Name[:1]) + field.Name[1:]
		}

		// 解引用字段值(处理 *T, **T 等)
		actualValue := resolveValue(value)

		// 如果字段是指针且为 nil
		if !actualValue.IsValid() {
			if doOmit {
				continue // 忽略 nil 字段
			}
			m[key] = nil
			continue
		}

		// 决定最终要存入 map 的值
		var finalValue interface{}

		switch actualValue.Kind() {
		case reflect.Struct:
			nestedMap := StructToMap(actualValue.Interface(), doOmit)
			if doOmit && len(nestedMap) == 0 {
				continue // 嵌套 struct 全为空,且要求 omitEmpty,则跳过
			}
			finalValue = nestedMap
		case reflect.Slice:
			elemType := actualValue.Type().Elem()
			sliceLen := actualValue.Len()
			if elemType.Kind() == reflect.Struct {
				sliceMap := make([]interface{}, sliceLen)
				for j := 0; j < sliceLen; j++ {
					item := actualValue.Index(j)
					itemResolved := resolveValue(item)
					if !itemResolved.IsValid() {
						sliceMap[j] = nil
					} else if itemResolved.Kind() == reflect.Struct {
						itemMap := StructToMap(itemResolved.Interface(), doOmit)
						sliceMap[j] = itemMap
						if len(itemMap) > 0 {

						}
					} else {
						sliceMap[j] = itemResolved.Interface()
						if !isEmptyValue(itemResolved) {

						}
					}
				}
				// 注意:即使 slice 全空,我们也保留空 slice [],除非你希望 omit 整个字段?
				// 这里策略:slice 本身不因内容为空而 omit 字段(因为 [] 也是有效状态)
				finalValue = sliceMap
			} else {
				// 普通 slice(如 []string, []*int)
				convertedSlice := make([]interface{}, sliceLen)
				for j := 0; j < sliceLen; j++ {
					elem := actualValue.Index(j)
					elemResolved := resolveValue(elem)
					if !elemResolved.IsValid() {
						convertedSlice[j] = nil
					} else {
						convertedSlice[j] = elemResolved.Interface()
					}
				}
				finalValue = convertedSlice
			}
		default:
			finalValue = actualValue.Interface()
		}

		// 检查是否应该忽略空值
		if doOmit {
			// 对于非 struct/slice 类型,检查是否为 zero value
			if actualValue.Kind() != reflect.Struct && actualValue.Kind() != reflect.Slice {
				if isEmptyValue(actualValue) {
					continue
				}
			}
			// struct 和 slice 的 omit 逻辑已在上面处理
		}

		m[key] = finalValue
	}

	return m
}

测试

go
package main

import (
	"demo/utils/bean_cover_util"
	"fmt"
)

type User struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`
	Email   *string  `json:"email"`
	Hobbies []string `json:"hobbies"`
	Address *Address `json:"address"`
}

type Address struct {
	City string `json:"city"`
	Code int    `json:"code"`
}

func main() {
	user := User{
		Name: "Alice",
		// Age 为 0(空值)
		// Email 为 nil
		Hobbies: []string{}, // 空 slice
		// Address 为 nil
	}

	// 保留所有字段(包括空值)
	fullMap := bean_cover_util.StructToMap(user)
	fmt.Println("Full map:", fullMap)
	// 输出: map[address:<nil> age:0 email:<nil> hobbies:[] name:Alice]

	// 忽略空值字段
	compactMap := bean_cover_util.StructToMap(user, true)
	fmt.Println("Compact map:", compactMap)
	// 输出: map[hobbies:[] name:Alice]  (其他都是空值或 nil,被忽略)
}

只跳过nil值

go
package bean_cover_util

import (
	"reflect"
	"strings"
)

// StructToMap 将结构体转换为 map[string]interface{}。
//
// 行为:
//   - 仅跳过值为 nil 的指针字段(如 *string, *User 等为 nil 时)
//   - 不跳过零值(0, "", false, empty slice/map 等都会保留)
//   - 非指针字段一律保留
//
// 支持:
//   - 值或指针类型的 struct
//   - json tag(忽略 ",xxx" 部分,支持 "-" 忽略)
//   - 嵌套 struct / slice of struct
//   - 自动解引用指针字段(非 nil 的)
func StructToMap(v interface{}) map[string]interface{} {
	m := make(map[string]interface{})
	rv := reflect.ValueOf(v)

	// 解引用顶层指针(支持传入 *Struct)
	if rv.Kind() == reflect.Ptr {
		if rv.IsNil() {
			return m
		}
		rv = rv.Elem()
	}

	if rv.Kind() != reflect.Struct {
		return m
	}

	rt := rv.Type()
	for i := 0; i < rv.NumField(); i++ {
		field := rt.Field(i)
		fieldValue := rv.Field(i)

		// 跳过未导出字段(小写开头)—— reflect 无法访问,但保险起见可加判断
		if !fieldValue.CanInterface() {
			continue
		}

		// 获取 json tag
		tag := field.Tag.Get("json")
		if tag == "-" {
			continue
		}

		// 去除 tag 中的选项(如 ",omitempty")
		if idx := strings.Index(tag, ","); idx != -1 {
			tag = tag[:idx]
		}

		key := tag
		if key == "" {
			// 使用小驼峰命名
			key = strings.ToLower(field.Name[:1]) + field.Name[1:]
		}

		// === 关键:跳过 nil 指针字段 ===
		if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
			continue
		}

		// 递归处理值(自动解引用非 nil 指针)
		finalValue := convertValue(fieldValue)

		m[key] = finalValue
	}

	return m
}

// convertValue 递归转换 reflect.Value 为 interface{}
func convertValue(v reflect.Value) interface{} {
	// 解引用所有指针(直到非指针)
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			// 理论上不会到这里,因为调用方已过滤 nil 指针
			return nil
		}
		v = v.Elem()
	}

	switch v.Kind() {
	case reflect.Struct:
		return StructToMap(v.Interface())
	case reflect.Slice:
		sliceLen := v.Len()
		result := make([]interface{}, sliceLen)
		for i := 0; i < sliceLen; i++ {
			result[i] = convertValue(v.Index(i))
		}
		return result
	case reflect.Map:
		mapLen := v.Len()
		if mapLen == 0 {
			// 返回空 map(保持类型信息丢失,但 interface{} 只能这样)
			return map[string]interface{}{}
		}
		result := make(map[string]interface{})
		for _, key := range v.MapKeys() {
			strKey := key.String() // 注意:key 必须可转 string,否则 panic
			result[strKey] = convertValue(v.MapIndex(key))
		}
		return result
	default:
		return v.Interface()
	}
}

测试

go
package main

import (
	"demo/utils/bean_cover_util"
	"fmt"
)

type User struct {
	Name    string   `json:"name"`
	Age     int      `json:"age"`     // 0 → 保留
	Email   *string  `json:"email"`   // nil → 跳过
	Mobile  *string  `json:"mobile"`  // 非 nil → 保留
	Tags    []string `json:"tags"`    // [] → 保留
	Address *Address `json:"address"` // nil → 跳过
}

type Address struct {
	City string `json:"city"`
	Code int    `json:"code"`
}

func main() {
	name := "Alice"
	mobile := "+123"

	user := User{
		Name:   name,
		Age:    0,          // 零值,但保留
		Email:  nil,        // nil 指针,跳过
		Mobile: &mobile,    // 非 nil,保留
		Tags:   []string{}, // 空 slice,保留
		// Address: nil,  // nil 指针,跳过
	}

	m := bean_cover_util.StructToMap(user)
	fmt.Printf("%+v\n", m)
}

如有转载或 CV 的请标注本站原文地址