如何在Golang中按动态字段名对结构数组进行排序

huangapple go评论128阅读模式
英文:

How to sort an struct array by dynamic field name in golang

问题

我想按照动态字段对一个结构数组进行排序。这是结构体的定义:

  1. type user struct{
  2. Name string `json:"name"`
  3. Age int `json:"age"`
  4. Status int `json:"status"`
  5. Type string `json:"type"`
  6. }

这是一个结构体数组:

  1. var UserArray []user

我需要按照给定的字段对这个数组进行排序,这个字段可以是user结构体的任何字段。但是我将从UI中以JSON标签的形式接收到这个排序字段。就像下面这样:

  1. sort := agnutil.GetQueryParamString(<json tag>, "sort", 0, "name")

我已经尝试了golang中的sort函数,但是如何动态使用它呢?

  1. sort.Slice(UserArray , func(i, j int) bool {
  2. return UserArray[i].<givenfield> < UserArray[j].<givenfield>
  3. })

请注意,上述代码中的<json tag><givenfield>需要替换为实际的JSON标签和给定的字段。

英文:

I want to sort a struct array by dynamic field. Here is the struct

  1. type user struct{
  2. Name string `json:&quot;name&quot;`
  3. Age int `json:&quot;age&quot;`
  4. Status int `json:&quot;status &quot;`
  5. Type string `json:&quot;type&quot;`
  6. }

This is an array of struct

  1. 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

  1. sort := agnutil.GetQueryParamString(&lt;json tag&gt;, &quot;sort&quot;, 0, &quot;name&quot;)

I have tried the sort function in golang but How to use that dynamically??

  1. sort.Slice(UserArray , func(i, j int) bool {
  2. return UserArray[i].&lt;givenfield&gt; &lt; UserArray[j].&lt;givenfield&gt;
  3. })

答案1

得分: 1

我想尝试按照字段的JSON标签对结构体切片进行排序,这里是我最终得到的代码,希望对大家有所帮助:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "sort"
  6. )
  7. func sortBy(jsonField string, arr []num) {
  8. if len(arr) < 1 {
  9. return
  10. }
  11. // 首先根据JSON标签找到字段
  12. valueType := reflect.TypeOf(arr[0])
  13. var field reflect.StructField
  14. for i := 0; i < valueType.NumField(); i++ {
  15. field = valueType.Field(i)
  16. if field.Tag.Get("json") == jsonField {
  17. break
  18. }
  19. }
  20. // 然后根据字段的类型进行排序
  21. sort.Slice(arr, func(i, j int) bool {
  22. v1 := reflect.ValueOf(arr[i]).FieldByName(field.Name)
  23. v2 := reflect.ValueOf(arr[j]).FieldByName(field.Name)
  24. switch field.Type.Name() {
  25. case "int":
  26. return int(v1.Int()) < int(v2.Int())
  27. case "string":
  28. return v1.String() < v2.String()
  29. case "bool":
  30. return !v1.Bool() // 返回较小的数字
  31. default:
  32. return false // 返回未修改的值
  33. }
  34. })
  35. fmt.Printf("\nsort by %s:\n", jsonField)
  36. prettyPrint(arr)
  37. }
  38. func prettyPrint(arr []num) {
  39. for _, v := range arr {
  40. fmt.Printf("%+v\n", v)
  41. }
  42. }
  43. type num struct {
  44. Id int `json:"id"`
  45. Name string `json:"name"`
  46. Big bool `json:"big"`
  47. }
  48. func main() {
  49. userArray := []num{
  50. {1, "one", false},
  51. {5, "five", false},
  52. {40, "fourty", true},
  53. {9, "nine", false},
  54. {60, "sixty", true},
  55. }
  56. fmt.Println("original:")
  57. prettyPrint(userArray)
  58. sortBy("id", userArray[:])
  59. sortBy("name", userArray[:])
  60. sortBy("big", userArray[:])
  61. }
  1. original:
  2. {Id:1 Name:one Big:false}
  3. {Id:5 Name:five Big:false}
  4. {Id:40 Name:fourty Big:true}
  5. {Id:9 Name:nine Big:false}
  6. {Id:60 Name:sixty Big:true}
  7. id排序
  8. {Id:1 Name:one Big:false}
  9. {Id:5 Name:five Big:false}
  10. {Id:9 Name:nine Big:false}
  11. {Id:40 Name:fourty Big:true}
  12. {Id:60 Name:sixty Big:true}
  13. name排序
  14. {Id:5 Name:five Big:false}
  15. {Id:40 Name:fourty Big:true}
  16. {Id:9 Name:nine Big:false}
  17. {Id:1 Name:one Big:false}
  18. {Id:60 Name:sixty Big:true}
  19. big排序
  20. {Id:1 Name:one Big:false}
  21. {Id:9 Name:nine Big:false}
  22. {Id:5 Name:five Big:false}
  23. {Id:40 Name:fourty Big:true}
  24. {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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;reflect&quot;
  5. &quot;sort&quot;
  6. )
  7. func sortBy(jsonField string, arr []num) {
  8. if len(arr) &lt; 1 {
  9. return
  10. }
  11. // first we find the field based on the json tag
  12. valueType := reflect.TypeOf(arr[0])
  13. var field reflect.StructField
  14. for i := 0; i &lt; valueType.NumField(); i++ {
  15. field = valueType.Field(i)
  16. if field.Tag.Get(&quot;json&quot;) == jsonField {
  17. break
  18. }
  19. }
  20. // then we sort based on the type of the field
  21. sort.Slice(arr, func(i, j int) bool {
  22. v1 := reflect.ValueOf(arr[i]).FieldByName(field.Name)
  23. v2 := reflect.ValueOf(arr[j]).FieldByName(field.Name)
  24. switch field.Type.Name() {
  25. case &quot;int&quot;:
  26. return int(v1.Int()) &lt; int(v2.Int())
  27. case &quot;string&quot;:
  28. return v1.String() &lt; v2.String()
  29. case &quot;bool&quot;:
  30. return !v1.Bool() // return small numbers first
  31. default:
  32. return false // return unmodified
  33. }
  34. })
  35. fmt.Printf(&quot;\nsort by %s:\n&quot;, jsonField)
  36. prettyPrint(arr)
  37. }
  38. func prettyPrint(arr []num) {
  39. for _, v := range arr {
  40. fmt.Printf(&quot;%+v\n&quot;, v)
  41. }
  42. }
  43. type num struct {
  44. Id int `json:&quot;id&quot;`
  45. Name string `json:&quot;name&quot;`
  46. Big bool `json:&quot;big&quot;`
  47. }
  48. func main() {
  49. userArray := []num{
  50. {1, &quot;one&quot;, false},
  51. {5, &quot;five&quot;, false},
  52. {40, &quot;fourty&quot;, true},
  53. {9, &quot;nine&quot;, false},
  54. {60, &quot;sixty&quot;, true},
  55. }
  56. fmt.Println(&quot;original:&quot;)
  57. prettyPrint(userArray)
  58. sortBy(&quot;id&quot;, userArray[:])
  59. sortBy(&quot;name&quot;, userArray[:])
  60. sortBy(&quot;big&quot;, userArray[:])
  61. }
  1. original:
  2. {Id:1 Name:one Big:false}
  3. {Id:5 Name:five Big:false}
  4. {Id:40 Name:fourty Big:true}
  5. {Id:9 Name:nine Big:false}
  6. {Id:60 Name:sixty Big:true}
  7. sort by id
  8. {Id:1 Name:one Big:false}
  9. {Id:5 Name:five Big:false}
  10. {Id:9 Name:nine Big:false}
  11. {Id:40 Name:fourty Big:true}
  12. {Id:60 Name:sixty Big:true}
  13. sort by name
  14. {Id:5 Name:five Big:false}
  15. {Id:40 Name:fourty Big:true}
  16. {Id:9 Name:nine Big:false}
  17. {Id:1 Name:one Big:false}
  18. {Id:60 Name:sixty Big:true}
  19. sort by big
  20. {Id:1 Name:one Big:false}
  21. {Id:9 Name:nine Big:false}
  22. {Id:5 Name:five Big:false}
  23. {Id:40 Name:fourty Big:true}
  24. {Id:60 Name:sixty Big:true}

答案2

得分: 0

问题有两个部分:根据JSON名称找到字段和按字段排序。

让我们从第二部分开始,编写一个按字段名称对切片进行排序的函数。以下是一个函数,可以对任何结构类型的结构切片或结构指针切片进行排序。请参阅注释以了解详细信息。

  1. // sortByField按命名字段对切片进行排序。
  2. // 切片参数必须是结构切片或结构指针切片。
  3. func sortByField(slice interface{}, fieldName string) error {
  4. v := reflect.ValueOf(slice)
  5. if v.Kind() != reflect.Slice {
  6. return fmt.Errorf("got %T, expected slice", slice)
  7. }
  8. // 获取切片元素类型。
  9. t := v.Type().Elem()
  10. // 处理结构指针。
  11. indirect := t.Kind() == reflect.Ptr
  12. if indirect {
  13. t = t.Elem()
  14. }
  15. if t.Kind() != reflect.Struct {
  16. return fmt.Errorf("got %T, expected slice of struct or pointer to struct", slice)
  17. }
  18. // 查找字段。
  19. sf, ok := t.FieldByName(fieldName)
  20. if !ok {
  21. return fmt.Errorf("field name %s not found", fieldName)
  22. }
  23. // 根据字段的类型创建一个less函数。
  24. var less func(a, b reflect.Value) bool
  25. switch sf.Type.Kind() {
  26. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  27. less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
  28. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  29. less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
  30. case reflect.Float32, reflect.Float64:
  31. less = func(a, b reflect.Value) bool { return a.Float() < b.Float() }
  32. case reflect.String:
  33. less = func(a, b reflect.Value) bool { return a.String() < b.String() }
  34. case reflect.Bool:
  35. less = func(a, b reflect.Value) bool { return !a.Bool() && b.Bool() }
  36. default:
  37. return fmt.Errorf("field type %s not supported", sf.Type)
  38. }
  39. // 排序!
  40. sort.Slice(slice, func(i, j int) bool {
  41. a := v.Index(i)
  42. b := v.Index(j)
  43. if indirect {
  44. a = a.Elem()
  45. b = b.Elem()
  46. }
  47. a = a.FieldByIndex(sf.Index)
  48. b = b.FieldByIndex(sf.Index)
  49. return less(a, b)
  50. })
  51. return nil
  52. }

将JSON名称映射到字段是复杂的。程序需要处理以下一般情况:通过嵌入提升的字段和可能出现的任何冲突,大小写不敏感,省略的JSON名称等。以下是处理问题中简单情况的函数:

  1. func simpleJSONToFieldName(t reflect.Type, name string) (string, bool) {
  2. for i := 0; i < t.NumField(); i++ {
  3. sf := t.Field(i)
  4. n := strings.Split(sf.Tag.Get("json"), ",")[0]
  5. if n == name {
  6. return sf.Name, true
  7. }
  8. }
  9. return "", false
  10. }

下面是如何将所有内容组合在一起的示例:

  1. var UserArray []user
  2. jsonName := request.FormValue("sort")
  3. fieldName, ok := simpleJSONToFieldName(reflect.TypeOf(user{}), jsonName)
  4. if !ok {
  5. // TODO: 处理错误的输入
  6. }
  7. if err := sortByField(UserArray, fieldName); err != nil {
  8. // TODO: 处理错误
  9. }

在 playground 上运行示例

英文:

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.

  1. // sortByField sorts slice by the named field.
  2. // The slice argument must be a slice of struct or
  3. // a slice of pointer to struct.
  4. func sortByField(slice interface{}, fieldName string) error {
  5. v := reflect.ValueOf(slice)
  6. if v.Kind() != reflect.Slice {
  7. return fmt.Errorf(&quot;got %T, expected slice&quot;, slice)
  8. }
  9. // Get slice element type.
  10. t := v.Type().Elem()
  11. // Handle pointer to struct.
  12. indirect := t.Kind() == reflect.Ptr
  13. if indirect {
  14. t = t.Elem()
  15. }
  16. if t.Kind() != reflect.Struct {
  17. return fmt.Errorf(&quot;got %T, expected slice of struct or pointer to struct&quot;, slice)
  18. }
  19. // Find the field.
  20. sf, ok := t.FieldByName(fieldName)
  21. if !ok {
  22. return fmt.Errorf(&quot;field name %s not found&quot;, fieldName)
  23. }
  24. // Create a less function based on the field&#39;s kind.
  25. var less func(a, b reflect.Value) bool
  26. switch sf.Type.Kind() {
  27. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  28. less = func(a, b reflect.Value) bool { return a.Int() &lt; b.Int() }
  29. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  30. less = func(a, b reflect.Value) bool { return a.Uint() &lt; b.Uint() }
  31. case reflect.Float32, reflect.Float64:
  32. less = func(a, b reflect.Value) bool { return a.Float() &lt; b.Float() }
  33. case reflect.String:
  34. less = func(a, b reflect.Value) bool { return a.String() &lt; b.String() }
  35. case reflect.Bool:
  36. less = func(a, b reflect.Value) bool { return !a.Bool() &amp;&amp; b.Bool() }
  37. default:
  38. return fmt.Errorf(&quot;field type %s not supported&quot;, sf.Type)
  39. }
  40. // Sort it!
  41. sort.Slice(slice, func(i, j int) bool {
  42. a := v.Index(i)
  43. b := v.Index(j)
  44. if indirect {
  45. a = a.Elem()
  46. b = b.Elem()
  47. }
  48. a = a.FieldByIndex(sf.Index)
  49. b = b.FieldByIndex(sf.Index)
  50. return less(a, b)
  51. })
  52. return nil
  53. }

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:

  1. func simpleJSONToFieldName(t reflect.Type, name string) (string, bool) {
  2. for i := 0; i &lt; t.NumField(); i++ {
  3. sf := t.Field(i)
  4. n := strings.Split(sf.Tag.Get(&quot;json&quot;), &quot;,&quot;)[0]
  5. if n == name {
  6. return sf.Name, true
  7. }
  8. }
  9. return &quot;&quot;, false
  10. }

Here's how to put it all together:

  1. var UserArray []user
  2. jsonName := request.FormValue(&quot;sort&quot;)
  3. fieldName, ok := simpleJSONToFieldName(reflect.TypeOf(user{}), jsonName)
  4. if !ok {
  5. // TODO: handle bad input
  6. }
  7. if err := sortByField(UserArray, fieldName); err != nil {
  8. // TODO: handle error
  9. }

Run an example on the playground.

huangapple
  • 本文由 发表于 2021年11月1日 02:10:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/69789292.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定