【问题标题】:JSON Unmarshal does not work with dynamic struct created by relectJSON Unmarshal 不适用于 relect 创建的动态结构
【发布时间】:2020-08-23 09:30:12
【问题描述】:

我正在尝试使用动态创建的结构来解析 JSON 文件,但显然我做错了什么。有人可以告诉我们我在做什么错吗:

structured := make(map[string][]reflect.StructField)
structured["Amqp1"] = []reflect.StructField{
    reflect.StructField{
        Name: "Test",
        Type: reflect.TypeOf(""),
        Tag:  reflect.StructTag(`json:"test"`),
    },
    reflect.StructField{
        Name: "Float",
        Type: reflect.TypeOf(5.5),
        Tag:  reflect.StructTag(`json:"float"`),
    },
    reflect.StructField{
        Name: "Connections",
        Type: reflect.TypeOf([]Connection{}),
        Tag:  reflect.StructTag(`json:"connections"`),
    },
}

sections := []reflect.StructField{}
for sect, params := range structured {
    sections = append(sections,
        reflect.StructField{
            Name: sect,
            Type: reflect.StructOf(params),
        },
    )
}

parsed := reflect.New(reflect.StructOf(sections)).Elem()
if err := json.Unmarshal([]byte(JSONConfigContent), &parsed); err != nil {
    fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
    os.Exit(1)
}

https://play.golang.org/p/C2I4Pduduyg

提前致谢。

【问题讨论】:

    标签: json go reflection unmarshalling reflect


    【解决方案1】:

    您想使用.Interface() 返回实际的底层值,该值应该是指向具体匿名结构的指针。

    请注意,reflect.New 函数返回一个 reflect.Value 表示一个 指针 指向指定类型的新零值。在这种情况下,Interface 方法将 指针 返回为 interface{},这就是 json.Unmarshal 所需要的。

    如果在解组后,您需要结构的非指针,您可以再次反射并使用reflect.ValueOf(parsed).Elem().Interface() 有效地取消引用指针。

    parsed := reflect.New(reflect.StructOf(sections)).Interface()
    if err := json.Unmarshal([]byte(JSONConfigContent), parsed); err != nil {
        fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
        os.Exit(1)
    }
    

    https://play.golang.org/p/Bzu1hUyKjvM

    【讨论】:

      【解决方案2】:

      reflect.New 返回一个表示指针的值。 更改第 69 行:

      fmt.Printf(">>> %v", &parsed)
      

      结果:

      >>> <struct { Amqp1 struct { Test string "json:\"test\""; Float float64 "json:\"float\""; Connections []main.Connection "json:\"connections\"" } } Value>
      

      【讨论】:

        【解决方案3】:

        我会推荐一些非常不同的东西。我通常会尽可能避免反思。您可以通过简单地为您期望的每种配置类型提供结构来完成您想要做的事情,然后进行初始“预解组”以确定您应该按名称实际使用的配置类型(在这种情况下是JSON 对象的键):

        package main
        
        import (
            "encoding/json"
            "fmt"
            "os"
        )
        
        //Amqp1 config struct
        type Amqp1 struct {
            Config struct {
                Test        string       `json:"test"`
                Float       float64      `json:"float"`
                Connections []Connection `json:"connections"`
            } `json:"Amqp1"`
        }
        
        //Connection struct
        type Connection struct {
            Type string `json:"type"`
            URL  string `json:"url"`
        }
        
        //JSONConfigContent json
        const JSONConfigContent = `{
            "Amqp1": {
                "test": "woobalooba",
                "float": 5.5,
                "connections": [
                    {"type": "test1", "url": "booyaka"},
                    {"type": "test2", "url": "foobar"}
                ]
            }
        }`
        
        func main() {
            configMap := make(map[string]interface{})
            if err := json.Unmarshal([]byte(JSONConfigContent), &configMap); err != nil {
                fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
                os.Exit(1)
            }
        
            //get config name
            var configName string
            for cfg := range configMap {
                configName = cfg
                break
            }
        
            //unmarshal appropriately
            switch configName {
            case "Amqp1":
                var amqp1 Amqp1
                if err := json.Unmarshal([]byte(JSONConfigContent), &amqp1); err != nil {
                    fmt.Printf("unable to parse data from provided configuration file: %s\n", err)
                    os.Exit(1)
                }
                fmt.Printf("%s >>\n", configName)
                fmt.Printf("Test: %s\n", amqp1.Config.Test)
                fmt.Printf("Float: %v\n", amqp1.Config.Float)
                fmt.Printf("Connections: %#v\n", amqp1.Config.Connections)
        
            default:
                fmt.Printf("unknown config encountered: %s\n", configName)
                os.Exit(1)
            }
        }
        

        最初的“预解组”发生在 main() 的第二行,到一个普通的映射,其中键是字符串,值是 interface{},因为你不在乎。您这样做只是为了获取实际的配置类型,这是第一个嵌套对象的键。

        你也可以run this on playground

        【讨论】:

        • 是的,当您在实施时知道配置文件的结构时,您可以这样做。可悲的是,在我的情况下这不是真的。
        • 在使用反射的时候还是要对结构有一定的了解。即使在您提供的示例中,您也提供了配置的名称。在我的解决方案中,配置的名称就是您所需要的。
        猜你喜欢
        • 2023-02-05
        • 2017-07-24
        • 2020-04-15
        • 1970-01-01
        • 1970-01-01
        • 2022-01-20
        • 2010-12-16
        • 2018-08-06
        • 1970-01-01
        相关资源
        最近更新 更多