sort map struct

为什么要有它

Go 的 map 本身没有顺序,而且每次遍历的顺序都是随机的——你没法「对 map 排序」。当业务需要「按某个 struct 字段把 map 里的值排好序再返回」时,只能先把值取出来放进切片,再对切片排序。

sort map struct 把这套动作封装成两个泛型函数:

  • MapStrcutSort:把 map[KEY]VALUE 的值抽成 []VALUE,再按指定字段排序后返回。
  • SliceStrcutSort:直接对 []VALUE 按 struct 的某个字段排序(借助 reflect 取字段,支持 int / float / string)。

代码

package helper

import (
	"reflect"
	"sort"
)

// MapStrcutSort 对map进行排序,值为struct类型
func MapStrcutSort[KEY comparable, VALUE any](m map[KEY]VALUE, fieldName string) []VALUE {
	var ret = make([]VALUE, 0)
	for _, d := range m {
		ret = append(ret, d)
	}
	SliceStrcutSort(ret, fieldName)
	return ret
}

// SliceStrcutSort 对slice进行排序,值为struct类型
func SliceStrcutSort[VALUE any](s []VALUE, fieldName string) {
	defer func() {
		if r := recover(); r != nil {
		}
	}()
	sliceValue := reflect.ValueOf(s)
	less := func(i, j int) bool {
		v1 := reflect.Indirect(sliceValue.Index(i))
		v2 := reflect.Indirect(sliceValue.Index(j))
		if v1.Kind() != reflect.Struct || v2.Kind() != reflect.Struct {
			return false
		}
		valueI := v1.FieldByName(fieldName)
		valueJ := v2.FieldByName(fieldName)
		switch valueI.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			return valueI.Int() < valueJ.Int()
		case reflect.Float32, reflect.Float64:
			return valueI.Float() < valueJ.Float()
		case reflect.String:
			return valueI.String() < valueJ.String()
		default:
			return false
		}
	}
	sort.Slice(s, less)
}

要点

  • map 不可排序,只能转切片MapStrcutSortfor range 把值摊平到 []VALUE,再交给 SliceStrcutSort 排序,返回的已经是「排好序的切片」,原 map 不会被改动(也无从改动)。
  • reflect 按字段名取值FieldByName(fieldName) 按名字拿字段,所以传入的 fieldName 必须是导出字段(首字母大写);非导出字段 reflect 取不到,会走 default 分支当成相等处理。
  • 支持的类型int 系列、float 系列、string。其他类型(bool、嵌套 struct、time.Time 等)在 switch 里落到 defaultless 返回 false——也就是被当成「相等」,排序对这些字段实际上不生效
  • reflect.Indirect 兼容指针元素:如果切片里放的是 *StructIndirect 会解引用到 struct;放的是值类型则原样返回,两种都能排。
  • panic 被静默吞掉defer recover() 保证字段名写错、或切片为空等情况下不崩,但代价是「排序失败也不会报错」——排查时需注意:万一没排上序,大概率是 fieldName 拼错或字段类型不支持。
  • 两个陷阱提醒
    • sort.Slice 不是稳定排序;若多个元素目标字段相等,它们的相对顺序不保证。需要确定性再排序就用 sort.SliceStable
    • 只对单个字段排序,不支持 A.B 这样的嵌套路径。

这两个函数的名字(MapStrcutSort / SliceStrcutSort)按原 helper 包命名保留;如想更规范可改名 MapStructSort / SliceStructSort。泛型约束 KEY comparable 是 map key 的硬性要求,VALUE any 则可接受任意结构体。