How to build a nested json/object recursively from a nested struct in Golang

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

How to build a nested json/object recursively from a nested struct in Golang

问题

如何使用以下结构来递归地构建嵌套的 JSON?

嵌套的结构可以有多个级别。

下面提到了示例结构和 JSON。

我在动态构建嵌套的 JSON/对象方面遇到了问题。

我使用了 reflect 包来访问结构。

我能够遍历数据,但无法构建相同的结构。

type Data struct {
	ID string 
	Name string 
	Types *Details
}

type Details struct {
	Customer int32 
	Countries   int32		
}

转换为:

{
  "Name":"Data",
  "Fields":[{
    "Name":"ID",
    "Type":"string"
  },
  {
    "Name":"Name",
    "Type":"string"
  },
  {
    "Name":"Details",
    "Type":"struct",
    "Fields":[{
      "Name":"Customer",
      "Type":"int32"
    },{
      "Name":"Countries",
      "Type":"int32"
    }]
}

我所做的工作如下:

package main

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

func check(e error) {
	if e != nil {
		panic(e)
	}
}

func getFields(t reflect.Type, prefix string) {
	switch t.Kind() {
	case reflect.Ptr:
		getFields(t.Elem(), "")
	case reflect.Struct:
		buildRecursiveFunction(t)
	}
}

func buildRecursiveFunction(t reflect.Type) map[string]interface{} {
	var jsonArr []interface{}
	temp := make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		if sf.Name != "state" && sf.Name != "unknownFields" && sf.Name != "sizeCache" {
			obj := make(map[string]interface{})
			varName := sf.Name
			varType := sf.Type.Kind()
			obj["Name"] = varName
			obj["Type"] = varType.String()

			if varType.String() == "ptr" {
				obj["Fields"] = buildRecursiveFunction(sf.Type.Elem())
			} else {
			}
			jsonArr = append(jsonArr, obj)
		}
	}
	jsonArrVal, _ := json.Marshal(jsonArr)
	fmt.Println(string(jsonArrVal))
	return temp

}

func main() {
	getFields(reflect.TypeOf(&DeviceEnv{}), "")
	// json.Marshal(AllAttributes)
}

任何帮助都将不胜感激。

英文:

How do i use the following struct to build a nested json recursively?

The nested struct can go to as many levels as it can.

Both the sample struct and json is mentioned below.

I'm having trouble in building a nested json/object dynamically.

I used the reflect package to access the struct.

I'm able to read through the data but not able to build the same.

type Data struct {
ID string 
Name string 
Types *Details
}
type Details struct {
Customer int32 
Countries   int32		
}

To:

{
&quot;Name&quot;:&quot;Data&quot;,
&quot;Fields&quot;:[{
&quot;Name&quot;:&quot;ID&quot;,
&quot;Type&quot;:&quot;string&quot;
},
{
&quot;Name&quot;:&quot;Name&quot;,
&quot;Type&quot;:&quot;string&quot;
},
{
&quot;Name&quot;:&quot;Details&quot;,
&quot;Type&quot;:&quot;struct&quot;,
&quot;Fields&quot;:[{
&quot;Name&quot;:&quot;Customer&quot;,
&quot;Type&quot;:&quot;int32&quot;
},{
&quot;Name&quot;:&quot;Countries&quot;,
&quot;Type&quot;:&quot;int32&quot;
}]
}

And whatever I have done so far, I have attached below:

package main
import (
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;reflect&quot;
)
func check(e error) {
if e != nil {
panic(e)
}
}
func getFields(t reflect.Type, prefix string) {
switch t.Kind() {
case reflect.Ptr:
getFields(t.Elem(), &quot;&quot;)
case reflect.Struct:
buildRecursiveFunction(t)
}
}
func buildRecursiveFunction(t reflect.Type) map[string]interface{} {
var jsonArr []interface{}
temp := make(map[string]interface{})
for i := 0; i &lt; t.NumField(); i++ {
sf := t.Field(i)
if sf.Name != &quot;state&quot; &amp;&amp; sf.Name != &quot;unknownFields&quot; &amp;&amp; sf.Name != &quot;sizeCache&quot; {
obj := make(map[string]interface{})
varName := sf.Name
varType := sf.Type.Kind()
obj[&quot;Name&quot;] = varName
obj[&quot;Type&quot;] = varType.String()
if varType.String() == &quot;ptr&quot; {
obj[&quot;Fields&quot;] = buildRecursiveFunction(sf.Type.Elem())
} else {
}
jsonArr = append(jsonArr, obj)
}
}
jsonArrVal, _ := json.Marshal(jsonArr)
fmt.Println(string(jsonArrVal))
return temp
}
func main() {
getFields(reflect.TypeOf(&amp;DeviceEnv{}), &quot;&quot;)
// json.Marshal(AllAttributes)
}

Any help is appreciated.

答案1

得分: 3

我已经在你的代码中进行了一些修改,并使其(在某种程度上)工作这里,但它似乎并不是以最佳方式结构化的。最好的方式是返回并构建一个递归对象,而不是尝试在进行过程中打印它(带有前缀?)。

关于定义一个TypeInfo结构体并拥有一个递归函数来填充它,你怎么看?我认为这会导致一个清晰的结构,并且允许调用者按照他们的意愿对结果进行JSON编组:

func main() {
	info := MakeTypeInfo("DeviceEnvironment", DeviceEnv{})
	b, err := json.MarshalIndent(info, "", "  ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", b)
}

type TypeInfo struct {
	Name   string     `json:"Name"`
	Type   string     `json:"Type"`
	Fields []TypeInfo `json:"Fields,omitempty"`
}

func MakeTypeInfo(name string, value interface{}) TypeInfo {
	return makeTypeInfo(name, reflect.TypeOf(value))
}

func makeTypeInfo(name string, t reflect.Type) TypeInfo {
	kind := t.Kind()
	switch kind {
	case reflect.Struct:
		var fields []TypeInfo
		for i := 0; i < t.NumField(); i++ {
			field := t.Field(i)
			fields = append(fields, makeTypeInfo(field.Name, field.Type))
		}
		return TypeInfo{Name: name, Type: kind.String(), Fields: fields}
	case reflect.Pointer:
		return makeTypeInfo(name, t.Elem())
	default:
		return TypeInfo{Name: name, Type: kind.String()}
	}
}

请注意,我还没有完成你在代码中展示的字段过滤(例如:"unknownFields")--但在reflect.Struct情况下添加它应该不难。

完整示例请参见Go Playground。

英文:

I've hacked around your code and got it (kind of) working here, but it doesn't seem like it's structured in the best way. Better to return and build a recursive object than trying to print it (with a prefix?) as you go.

What about defining a TypeInfo struct and having a recursive function that populates that? I think that leads to a clear structure, and it allows the caller to JSON-marshal the result as they want to:

func main() {
	info := MakeTypeInfo(&quot;DeviceEnvironment&quot;, DeviceEnv{})
	b, err := json.MarshalIndent(info, &quot;&quot;, &quot;  &quot;)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&quot;%s\n&quot;, b)
}

type TypeInfo struct {
	Name   string     `json:&quot;Name&quot;`
	Type   string     `json:&quot;Type&quot;`
	Fields []TypeInfo `json:&quot;Fields,omitempty&quot;`
}

func MakeTypeInfo(name string, value interface{}) TypeInfo {
	return makeTypeInfo(name, reflect.TypeOf(value))
}

func makeTypeInfo(name string, t reflect.Type) TypeInfo {
	kind := t.Kind()
	switch kind {
	case reflect.Struct:
		var fields []TypeInfo
		for i := 0; i &lt; t.NumField(); i++ {
			field := t.Field(i)
			fields = append(fields, makeTypeInfo(field.Name, field.Type))
		}
		return TypeInfo{Name: name, Type: kind.String(), Fields: fields}
	case reflect.Pointer:
		return makeTypeInfo(name, t.Elem())
	default:
		return TypeInfo{Name: name, Type: kind.String()}
	}
}

Note that I haven't done the field filtering (eg: "unknownFields") that you've shown in your code -- shouldn't be hard to add inside the reflect.Struct case though.

Full example in Go Playground.

huangapple
  • 本文由 发表于 2022年6月28日 06:15:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/72778820.html
匿名

发表评论

匿名网友

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

确定