【问题标题】:Converting map to struct将映射转换为结构
【发布时间】:2014-11-04 20:56:40
【问题描述】:

我正在尝试在 Go 中创建一个通用方法,该方法将使用来自 map[string]interface{} 的数据填充 struct。例如,方法签名和用法可能如下所示:

func FillStruct(data map[string]interface{}, result interface{}) {
    ...
}

type MyStruct struct {
    Name string
    Age  int64
}

myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"]  = 23

result := &MyStruct{}
FillStruct(myData, result)

// result now has Name set to "Tony" and Age set to 23

我知道这可以使用 JSON 作为中介来完成;还有其他更有效的方法吗?

【问题讨论】:

  • 使用 JSON 作为中介无论如何都会使用反射.. 假设您将使用 stdlib encoding/json 包来执行该中间步骤.. 你能给出一个示例映射和示例结构吗这个方法可以用吗?
  • 是的,这就是我试图避免使用 JSON 的原因。似乎希望有一种我不知道的更有效的方法。
  • 你能举个例子吗?如-显示一些伪代码来演示此方法将做什么?
  • 嗯……unsafe 包可能有办法……但我不敢尝试。除此之外.. 反射是必需的,因为您需要能够查询与类型关联的元数据,以便将数据放入其属性中。将其包装在 json.Marshal + json.Decode 调用中会相当直接。但这是反射的两倍。
  • 我删除了关于反射的评论。我对尽可能高效地执行此操作更感兴趣。如果这意味着使用反射就可以了。

标签: go


【解决方案1】:

最简单的方法是使用https://github.com/mitchellh/mapstructure

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

如果你想自己做,你可以这样做:

http://play.golang.org/p/tN8mxT_V9h

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("Provided value type didn't match obj field type")
    }

    structFieldValue.Set(val)
    return nil
}

type MyStruct struct {
    Name string
    Age  int64
}

func (s *MyStruct) FillStruct(m map[string]interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

func main() {
    myData := make(map[string]interface{})
    myData["Name"] = "Tony"
    myData["Age"] = int64(23)

    result := &MyStruct{}
    err := result.FillStruct(myData)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}

【讨论】:

  • 谢谢。我正在使用稍微修改过的版本。 play.golang.org/p/_JuMm6HMnU
  • 我希望 FillStruct 对我所有的各种结构都有行为,而不必为每个结构定义 func (s MyStr...) FillStruct ...。是否可以为基本结构定义 FillStruct,然后让我的所有其他结构“继承”该行为?在上面的范例中这是不可能的,因为只有基本结构......在这种情况下,“MyStruct”实际上会迭代它的字段
  • 我的意思是你可以让它适用于任何类似这样的结构:play.golang.org/p/0weG38IUA9
  • Mystruct可以实现标签吗?
  • @abhishek 肯定会为首先编组到文本然后解组而付出性能损失。这种方法当然也更简单。这是一种权衡,通常我会选择更简单的解决方案。我回答了这个解决方案,因为问题表明“我知道这可以使用 JSON 作为中介来完成;还有另一种更有效的方法吗?”。此解决方案会更高效,JSON 解决方案通常更易于实施和推理。
【解决方案2】:

Hashicorp 的 https://github.com/mitchellh/mapstructure 库开箱即用:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

第二个result 参数必须是结构的地址。

【讨论】:

【解决方案3】:
  • 最简单的方法是使用encoding/json

举例:

package main
import (
    "fmt"
    "encoding/json"
)

type MyAddress struct {
    House string
    School string
}
type Student struct {
    Id int64
    Name string
    Scores float32
    Address MyAddress
    Labels []string
}

func Test() {

    dict := make(map[string]interface{})
    dict["id"] = 201902181425       // int
    dict["name"] = "jackytse"       // string
    dict["scores"] = 123.456        // float
    dict["address"] = map[string]string{"house":"my house", "school":"my school"}   // map
    dict["labels"] = []string{"aries", "warmhearted", "frank"}      // slice

    jsonbody, err := json.Marshal(dict)
    if err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    student := Student{}
    if err := json.Unmarshal(jsonbody, &student); err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    fmt.Printf("%#v\n", student)
}

func main() {
    Test()
}

【讨论】:

  • 谢谢@jackytse。这实际上是最好的方法!地图结构通常不适用于界面中的嵌套地图。所以最好考虑一个地图字符串接口,把它当作一个json来处理。
  • 上述 sn-p 的游乐场链接:play.golang.org/p/JaKxETAbsnT
  • 这是最好的方法,为什么这不是公认的答案
  • @karthikcru 因为这正是 OP 说他们想要避免做的事情,请参阅“我知道这可以使用 JSON 作为中介来完成;还有另一种更有效的方法吗?”跨度>
【解决方案4】:

你可以做到......它可能会有点难看,你会在映射类型方面面临一些试验和错误......但这是它的基本要点:

func FillStruct(data map[string]interface{}, result interface{}) {
    t := reflect.ValueOf(result).Elem()
    for k, v := range data {
        val := t.FieldByName(k)
        val.Set(reflect.ValueOf(v))
    }
}

工作样本:http://play.golang.org/p/PYHz63sbvL

【讨论】:

  • 这似乎对零值感到恐慌:reflect: call of reflect.Value.Set on zero Value
  • @JamesTaylor 是的。我的回答假设您确切知道要映射哪些字段。如果您正在寻求具有更多错误处理的类似答案(包括您遇到的错误),我会建议 Daves 回答。
【解决方案5】:

有两个步骤:

  1. 将接口转换为 JSON 字节
  2. 将 JSON 字节转换为结构

下面是一个例子:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)

【讨论】:

    【解决方案6】:

    您可以通过 JSON 进行往返:

    package main
    
    import (
       "bytes"
       "encoding/json"
    )
    
    func transcode(in, out interface{}) {
       buf := new(bytes.Buffer)
       json.NewEncoder(buf).Encode(in)
       json.NewDecoder(buf).Decode(out)
    }
    

    例子:

    package main
    import "fmt"
    
    type myStruct struct {
       Name string
       Age  int64
    }
    
    func main() {
       myData := map[string]interface{}{
          "Name": "Tony",
          "Age": 23,
       }
       var result myStruct
       transcode(myData, &result)
       fmt.Printf("%+v\n", result) // {Name:Tony Age:23}
    }
    

    【讨论】:

      【解决方案7】:

      我改编了戴夫的回答,并添加了递归功能。我仍在开发更用户友好的版本。例如,map 中的数字字符串应该可以在 struct 中转换为 int。

      package main
      
      import (
          "fmt"
          "reflect"
      )
      
      func SetField(obj interface{}, name string, value interface{}) error {
      
          structValue := reflect.ValueOf(obj).Elem()
          fieldVal := structValue.FieldByName(name)
      
          if !fieldVal.IsValid() {
              return fmt.Errorf("No such field: %s in obj", name)
          }
      
          if !fieldVal.CanSet() {
              return fmt.Errorf("Cannot set %s field value", name)
          }
      
          val := reflect.ValueOf(value)
      
          if fieldVal.Type() != val.Type() {
      
              if m,ok := value.(map[string]interface{}); ok {
      
                  // if field value is struct
                  if fieldVal.Kind() == reflect.Struct {
                      return FillStruct(m, fieldVal.Addr().Interface())
                  }
      
                  // if field value is a pointer to struct
                  if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
                      if fieldVal.IsNil() {
                          fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
                      }
                      // fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
                      return FillStruct(m, fieldVal.Interface())
                  }
      
              }
      
              return fmt.Errorf("Provided value type didn't match obj field type")
          }
      
          fieldVal.Set(val)
          return nil
      
      }
      
      func FillStruct(m map[string]interface{}, s interface{}) error {
          for k, v := range m {
              err := SetField(s, k, v)
              if err != nil {
                  return err
              }
          }
          return nil
      }
      
      type OtherStruct struct {
          Name string
          Age  int64
      }
      
      
      type MyStruct struct {
          Name string
          Age  int64
          OtherStruct *OtherStruct
      }
      
      
      
      func main() {
          myData := make(map[string]interface{})
          myData["Name"]        = "Tony"
          myData["Age"]         = int64(23)
          OtherStruct := make(map[string]interface{})
          myData["OtherStruct"] = OtherStruct
          OtherStruct["Name"]   = "roxma"
          OtherStruct["Age"]    = int64(23)
      
          result := &MyStruct{}
          err := FillStruct(myData,result)
          fmt.Println(err)
          fmt.Printf("%v %v\n",result,result.OtherStruct)
      }
      

      【讨论】:

        猜你喜欢
        • 2016-07-30
        • 2012-06-11
        • 2022-01-19
        • 1970-01-01
        • 2021-12-21
        • 1970-01-01
        • 1970-01-01
        • 2012-06-16
        • 1970-01-01
        相关资源
        最近更新 更多