英文:
How to sort an struct array by dynamic field name in golang
问题
我想按照动态字段对一个结构数组进行排序。这是结构体的定义:
type user struct{
Name string `json:"name"`
Age int `json:"age"`
Status int `json:"status"`
Type string `json:"type"`
}
这是一个结构体数组:
var UserArray []user
我需要按照给定的字段对这个数组进行排序,这个字段可以是user结构体的任何字段。但是我将从UI中以JSON标签的形式接收到这个排序字段。就像下面这样:
sort := agnutil.GetQueryParamString(<json tag>, "sort", 0, "name")
我已经尝试了golang中的sort函数,但是如何动态使用它呢?
sort.Slice(UserArray , func(i, j int) bool {
return UserArray[i].<givenfield> < UserArray[j].<givenfield>
})
请注意,上述代码中的<json tag>
和<givenfield>
需要替换为实际的JSON标签和给定的字段。
英文:
I want to sort a struct array by dynamic field. Here is the struct
type user struct{
Name string `json:"name"`
Age int `json:"age"`
Status int `json:"status "`
Type string `json:"type"`
}
This is an array of struct
var UserArray []user
I have to sort this array by a given field that can be any field of user struct. but I will receive that sorting field from UI as a JSON tag. Like below
sort := agnutil.GetQueryParamString(<json tag>, "sort", 0, "name")
I have tried the sort function in golang but How to use that dynamically??
sort.Slice(UserArray , func(i, j int) bool {
return UserArray[i].<givenfield> < UserArray[j].<givenfield>
})
答案1
得分: 1
我想尝试按照字段的JSON标签对结构体切片进行排序,这里是我最终得到的代码,希望对大家有所帮助:
package main
import (
"fmt"
"reflect"
"sort"
)
func sortBy(jsonField string, arr []num) {
if len(arr) < 1 {
return
}
// 首先根据JSON标签找到字段
valueType := reflect.TypeOf(arr[0])
var field reflect.StructField
for i := 0; i < valueType.NumField(); i++ {
field = valueType.Field(i)
if field.Tag.Get("json") == jsonField {
break
}
}
// 然后根据字段的类型进行排序
sort.Slice(arr, func(i, j int) bool {
v1 := reflect.ValueOf(arr[i]).FieldByName(field.Name)
v2 := reflect.ValueOf(arr[j]).FieldByName(field.Name)
switch field.Type.Name() {
case "int":
return int(v1.Int()) < int(v2.Int())
case "string":
return v1.String() < v2.String()
case "bool":
return !v1.Bool() // 返回较小的数字
default:
return false // 返回未修改的值
}
})
fmt.Printf("\nsort by %s:\n", jsonField)
prettyPrint(arr)
}
func prettyPrint(arr []num) {
for _, v := range arr {
fmt.Printf("%+v\n", v)
}
}
type num struct {
Id int `json:"id"`
Name string `json:"name"`
Big bool `json:"big"`
}
func main() {
userArray := []num{
{1, "one", false},
{5, "five", false},
{40, "fourty", true},
{9, "nine", false},
{60, "sixty", true},
}
fmt.Println("original:")
prettyPrint(userArray)
sortBy("id", userArray[:])
sortBy("name", userArray[:])
sortBy("big", userArray[:])
}
original:
{Id:1 Name:one Big:false}
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:9 Name:nine Big:false}
{Id:60 Name:sixty Big:true}
按id排序:
{Id:1 Name:one Big:false}
{Id:5 Name:five Big:false}
{Id:9 Name:nine Big:false}
{Id:40 Name:fourty Big:true}
{Id:60 Name:sixty Big:true}
按name排序:
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:9 Name:nine Big:false}
{Id:1 Name:one Big:false}
{Id:60 Name:sixty Big:true}
按big排序:
{Id:1 Name:one Big:false}
{Id:9 Name:nine Big:false}
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:60 Name:sixty Big:true}
英文:
I wanted to try sorting a slice of structs by the field's json tag, here is what I ended up having, in case it helps anyone:
package main
import (
"fmt"
"reflect"
"sort"
)
func sortBy(jsonField string, arr []num) {
if len(arr) < 1 {
return
}
// first we find the field based on the json tag
valueType := reflect.TypeOf(arr[0])
var field reflect.StructField
for i := 0; i < valueType.NumField(); i++ {
field = valueType.Field(i)
if field.Tag.Get("json") == jsonField {
break
}
}
// then we sort based on the type of the field
sort.Slice(arr, func(i, j int) bool {
v1 := reflect.ValueOf(arr[i]).FieldByName(field.Name)
v2 := reflect.ValueOf(arr[j]).FieldByName(field.Name)
switch field.Type.Name() {
case "int":
return int(v1.Int()) < int(v2.Int())
case "string":
return v1.String() < v2.String()
case "bool":
return !v1.Bool() // return small numbers first
default:
return false // return unmodified
}
})
fmt.Printf("\nsort by %s:\n", jsonField)
prettyPrint(arr)
}
func prettyPrint(arr []num) {
for _, v := range arr {
fmt.Printf("%+v\n", v)
}
}
type num struct {
Id int `json:"id"`
Name string `json:"name"`
Big bool `json:"big"`
}
func main() {
userArray := []num{
{1, "one", false},
{5, "five", false},
{40, "fourty", true},
{9, "nine", false},
{60, "sixty", true},
}
fmt.Println("original:")
prettyPrint(userArray)
sortBy("id", userArray[:])
sortBy("name", userArray[:])
sortBy("big", userArray[:])
}
original:
{Id:1 Name:one Big:false}
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:9 Name:nine Big:false}
{Id:60 Name:sixty Big:true}
sort by id
{Id:1 Name:one Big:false}
{Id:5 Name:five Big:false}
{Id:9 Name:nine Big:false}
{Id:40 Name:fourty Big:true}
{Id:60 Name:sixty Big:true}
sort by name
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:9 Name:nine Big:false}
{Id:1 Name:one Big:false}
{Id:60 Name:sixty Big:true}
sort by big
{Id:1 Name:one Big:false}
{Id:9 Name:nine Big:false}
{Id:5 Name:five Big:false}
{Id:40 Name:fourty Big:true}
{Id:60 Name:sixty Big:true}
答案2
得分: 0
问题有两个部分:根据JSON名称找到字段和按字段排序。
让我们从第二部分开始,编写一个按字段名称对切片进行排序的函数。以下是一个函数,可以对任何结构类型的结构切片或结构指针切片进行排序。请参阅注释以了解详细信息。
// sortByField按命名字段对切片进行排序。
// 切片参数必须是结构切片或结构指针切片。
func sortByField(slice interface{}, fieldName string) error {
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Slice {
return fmt.Errorf("got %T, expected slice", slice)
}
// 获取切片元素类型。
t := v.Type().Elem()
// 处理结构指针。
indirect := t.Kind() == reflect.Ptr
if indirect {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return fmt.Errorf("got %T, expected slice of struct or pointer to struct", slice)
}
// 查找字段。
sf, ok := t.FieldByName(fieldName)
if !ok {
return fmt.Errorf("field name %s not found", fieldName)
}
// 根据字段的类型创建一个less函数。
var less func(a, b reflect.Value) bool
switch sf.Type.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
case reflect.Float32, reflect.Float64:
less = func(a, b reflect.Value) bool { return a.Float() < b.Float() }
case reflect.String:
less = func(a, b reflect.Value) bool { return a.String() < b.String() }
case reflect.Bool:
less = func(a, b reflect.Value) bool { return !a.Bool() && b.Bool() }
default:
return fmt.Errorf("field type %s not supported", sf.Type)
}
// 排序!
sort.Slice(slice, func(i, j int) bool {
a := v.Index(i)
b := v.Index(j)
if indirect {
a = a.Elem()
b = b.Elem()
}
a = a.FieldByIndex(sf.Index)
b = b.FieldByIndex(sf.Index)
return less(a, b)
})
return nil
}
将JSON名称映射到字段是复杂的。程序需要处理以下一般情况:通过嵌入提升的字段和可能出现的任何冲突,大小写不敏感,省略的JSON名称等。以下是处理问题中简单情况的函数:
func simpleJSONToFieldName(t reflect.Type, name string) (string, bool) {
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
n := strings.Split(sf.Tag.Get("json"), ",")[0]
if n == name {
return sf.Name, true
}
}
return "", false
}
下面是如何将所有内容组合在一起的示例:
var UserArray []user
jsonName := request.FormValue("sort")
fieldName, ok := simpleJSONToFieldName(reflect.TypeOf(user{}), jsonName)
if !ok {
// TODO: 处理错误的输入
}
if err := sortByField(UserArray, fieldName); err != nil {
// TODO: 处理错误
}
英文:
There are two parts to the problem: finding the field given the JSON name and sorting by the field.
Let's start with the code for the second part, sort a slice by field name. Here's an function that sorts a slice of struct or slice of pointer to struct for any struct type. See the commentary for details.
// sortByField sorts slice by the named field.
// The slice argument must be a slice of struct or
// a slice of pointer to struct.
func sortByField(slice interface{}, fieldName string) error {
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Slice {
return fmt.Errorf("got %T, expected slice", slice)
}
// Get slice element type.
t := v.Type().Elem()
// Handle pointer to struct.
indirect := t.Kind() == reflect.Ptr
if indirect {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return fmt.Errorf("got %T, expected slice of struct or pointer to struct", slice)
}
// Find the field.
sf, ok := t.FieldByName(fieldName)
if !ok {
return fmt.Errorf("field name %s not found", fieldName)
}
// Create a less function based on the field's kind.
var less func(a, b reflect.Value) bool
switch sf.Type.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
case reflect.Float32, reflect.Float64:
less = func(a, b reflect.Value) bool { return a.Float() < b.Float() }
case reflect.String:
less = func(a, b reflect.Value) bool { return a.String() < b.String() }
case reflect.Bool:
less = func(a, b reflect.Value) bool { return !a.Bool() && b.Bool() }
default:
return fmt.Errorf("field type %s not supported", sf.Type)
}
// Sort it!
sort.Slice(slice, func(i, j int) bool {
a := v.Index(i)
b := v.Index(j)
if indirect {
a = a.Elem()
b = b.Elem()
}
a = a.FieldByIndex(sf.Index)
b = b.FieldByIndex(sf.Index)
return less(a, b)
})
return nil
}
Mapping the JSON name to a field is complicated. The program needs to handle the following in the general case: fields promoted by embedding and any conflicts that arise, case insensitivity, elided JSON name, etc. Here's a function that handles the simple case in the question:
func simpleJSONToFieldName(t reflect.Type, name string) (string, bool) {
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
n := strings.Split(sf.Tag.Get("json"), ",")[0]
if n == name {
return sf.Name, true
}
}
return "", false
}
Here's how to put it all together:
var UserArray []user
jsonName := request.FormValue("sort")
fieldName, ok := simpleJSONToFieldName(reflect.TypeOf(user{}), jsonName)
if !ok {
// TODO: handle bad input
}
if err := sortByField(UserArray, fieldName); err != nil {
// TODO: handle error
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论