将未知结构的JSON输出添加到任意字段

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

Adding Arbitrary fields to json output of an unknown struct

问题

在这个stackoverflow帖子中,解释了如何通过将其用作匿名字段来向golang结构体添加任意字段。如果你正在使用已知的结构体类型,这种方法可以正常工作,但是我想知道在处理未知的结构体或接口时如何做同样的事情。

我编写了以下示例来进行演示:

package main

import (
	"os"
	"encoding/json"
	"fmt"
)

type example interface{}
type Data struct {
	Name string
}

func printInterface(val interface{})	{
	example1 := struct {
		example
		Extra string
	}{
		example: val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example1)
}

func printStructPointer(val *Data)	{
	example2 := struct {
		*Data
		Extra string
	}{
		Data: val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example2)
}


func main() {
	d := Data{Name:"name"}
	fmt.Println("Example 1:")
	printInterface(&d)
	fmt.Println("Example 2:")
	printStructPointer(&d)
}

这将打印以下内容:

Example 1:
{"example":{"Name":"name"},"Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}

我假设我正在printInterface函数中工作,如何使JSON输出的格式与printStructPointer函数的JSON输出格式相同?

英文:

In this stackoverflow post it's explained how to add arbitrary fields to a golang struct by using it as an anonymous. This works fine if you are working with known struct types, but I'm wondering how to do the same thing when dealing with an unknown struct or interface.

I wrote the following example to demonstrate:

package main

import (
	"os"
	"encoding/json"
	"fmt"
)

type example interface{}
type Data struct {
	Name string
}

func printInterface(val interface{})	{
	example1 := struct {
		example
		Extra string
	}{
		example: val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example1)
}

func printStructPointer(val *Data)	{
	example2 := struct {
		*Data
		Extra string
	}{
		Data: val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example2)
}


func main() {
	d := Data{Name:"name"}
	fmt.Println("Example 1:")
	printInterface(&d)
	fmt.Println("Example 2:")
	printStructPointer(&d)
}

This prints the following:

Example 1:
{"example":{"Name":"name"},"Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}

I'm so assuming that I was working within printInterface how do get the JSON output to look like the JSON output of printStructPointer?

答案1

得分: 2

printInterface()printStructPointer()之间有一个重要的区别。第一个函数嵌入了一个接口类型,而第二个函数嵌入了一个结构体类型(更具体地说是一个指向结构体类型的指针)。

当你嵌入一个结构体类型(或结构体指针类型)时,被嵌入类型的字段会被提升,所以在第二个例子中,可以有效地写example2.Name。当你嵌入一个接口类型时,接口没有字段,所以不会有字段被提升。因此,无论接口值包装的是结构体还是结构体指针,该结构体的字段都不会被提升(也不能被提升)。

因此,在printInterface()中,包装结构体的接口在JSON结果中不会被"展开"。

使用反射生成动态类型解决问题

解决这个问题的一种方法是在运行时使用反射(reflect包)生成一个动态类型。这个新类型将是一个结构体,它将包含一个匿名结构体字段,该字段的类型是传递的接口所包装的类型,并且还将包含我们的额外字段(类型为string)。

代码示例如下:

func printInterface(val interface{}) {
    t2 := reflect.StructOf([]reflect.StructField{
        reflect.StructField{
            Name:      "X",
            Anonymous: true,
            Type:      reflect.TypeOf(val),
        },
        reflect.StructField{
            Name: "Extra",
            Type: reflect.TypeOf(""),
        },
    })

    v2 := reflect.New(t2).Elem()
    v2.Field(0).Set(reflect.ValueOf(val))
    v2.FieldByName("Extra").SetString("text")

    json.NewEncoder(os.Stdout).Encode(v2.Interface())
}

输出结果与预期相同(在Go Playground上尝试):

Example 1:
{"Name":"name","Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}

使用两次编组解决问题

另一种方法是将值进行编组,然后将其解组为一个映射,添加额外的字段,然后再次进行编组:

func printInterface(val interface{}) error {
    data, err := json.Marshal(val)
    if err != nil {
        return err
    }

    v2 := map[string]interface{}{}
    if err := json.Unmarshal(data, &v2); err != nil {
        return err
    }

    v2["Extra"] = "text"
    return json.NewEncoder(os.Stdout).Encode(v2)
}

输出结果与前面的方法相同。在Go Playground上尝试。

这种解决方案更简单,更易于理解,但是由于需要进行两次编组,所以速度较慢。另外请注意,在这个例子中,结果中的字段可能以不同的顺序出现,因为在Go中,映射的迭代顺序是不确定的(有关详细信息,请参阅https://stackoverflow.com/questions/28930416/why-cant-go-iterate-maps-in-insertion-order/28931555#28931555)。

英文:

There's an important difference between printInterface() and printStructPointer(). The first one embeds an interface type, while the second embeds a struct type (more specifically a pointer to a struct type).

When you embed a struct (or pointer to struct) type, the fields of the embedded type get promoted, so in the 2nd example it will be valid to write example2.Name. When you embed an interface type, an interface does not have fields, so no fields will be promoted. So it doesn't matter if the interface value wraps a struct (or pointer to struct), fields of that struct won't get promoted (they can't be).

Thus, in the printInterface() the interface wrapping a struct won't get "flattened" in the JSON result.

Solving it with generating a dynamic type using reflection

One way to solve this is to generate a dynamic type at runtime, using reflection (reflect package). This new type will be a struct, and it will contain an anonymous struct field being of the type that is wrapped in the passed interface, and will also contain our extra field (of type string).

This is how it could look like:

func printInterface(val interface{}) {
	t2 := reflect.StructOf([]reflect.StructField{
		reflect.StructField{
			Name:      "X",
			Anonymous: true,
			Type:      reflect.TypeOf(val),
		},
		reflect.StructField{
			Name: "Extra",
			Type: reflect.TypeOf(""),
		},
	})

	v2 := reflect.New(t2).Elem()
	v2.Field(0).Set(reflect.ValueOf(val))
	v2.FieldByName("Extra").SetString("text")

	json.NewEncoder(os.Stdout).Encode(v2.Interface())
}

Output is as expected (try it on the Go Playground):

Example 1:
{"Name":"name","Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}

Solving it with marshaling twice

Another way would be to marshal the value, unmarshal it into a map, add the extra field and marshal it again:

func printInterface(val interface{}) error {
	data, err := json.Marshal(val)
	if err != nil {
		return err
	}

	v2 := map[string]interface{}{}
	if err := json.Unmarshal(data, &v2); err != nil {
		return err
	}

	v2["Extra"] = "text"
	return json.NewEncoder(os.Stdout).Encode(v2)
}

Output is the same. Try it on the Go Playground.

This solution is simpler, easier to follow, but it's slower as it marshals twice. Also note that in this example the fields in the result might be in different order, as iteration order on a map is not specified in Go (for details see https://stackoverflow.com/questions/28930416/why-cant-go-iterate-maps-in-insertion-order/28931555#28931555).

答案2

得分: 0

以下是翻译好的内容:

这是一种方法:

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type example interface{}
type Data struct {
	Name string
}

func printInterface(val interface{}) {
	if d, ok := val.(*Data); ok {
		fmt.Println("对于val断言为*Data类型是正确的")
		printStructPointer(d)
	} else {
		fmt.Println("对于val断言为*Data类型是错误的")
	}

}

func printStructPointer(val *Data) {

	example2 := struct {
		*Data
		Extra string
	}{
		Data:  val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example2)
}

func main() {
	d := Data{Name: "Testing"}
	fmt.Println("示例1:")
	printInterface(&d)
	fmt.Println("示例2:")
	printStructPointer(&d)
}

Playground链接:https://play.golang.org/p/OPotpTyUEz

如果有多种类型,你也可以使用类型开关进行断言。希望对你有帮助!

英文:

Here's one way:

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type example interface{}
type Data struct {
	Name string
}

func printInterface(val interface{}) {
	if d, ok := val.(*Data); ok {
		fmt.Println("Asserting type *Data for val is OK")
		printStructPointer(d)
	} else {
		fmt.Println("Asserting type *Data for val is NOT OK")
	}

}

func printStructPointer(val *Data) {

	example2 := struct {
		*Data
		Extra string
	}{
		Data:  val,
		Extra: "text",
	}
	json.NewEncoder(os.Stdout).Encode(example2)
}

func main() {
	d := Data{Name: "Testing"}
	fmt.Println("Example 1:")
	printInterface(&d)
	fmt.Println("Example 2:")
	printStructPointer(&d)
}

Playground: https://play.golang.org/p/OPotpTyUEz

You can also possibly use a type switch for an assertion, esp if you have many types. Hope this helps!

huangapple
  • 本文由 发表于 2017年3月10日 11:30:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/42709680.html
匿名

发表评论

匿名网友

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

确定