英文:
Merging two different structs and marshalling them - Golang
问题
假设我有以下的第一个结构体:
type Person struct {
Name string `json:"person_name"`
Age int `json:"person_age"`
Data map[string]interface{} `json:"data"`
}
我想要将一个包含上述结构体的数组进行编组。到目前为止,一切都正常,我收到的一个示例响应如下:
[
{
"person_name":"name",
"person_age":12,"data":{}
},
{
"person_name":"name2",
"person_age":12,"data":{}
}
]
现在,我需要在这里追加另一个结构体,并且最终的响应应该如下:
[
{
"person_name":"name",
"person_age":12,"data":{}
},
{
"person_name":"name2",
"person_age":12,"data":{}
},
{
"newData":"value"
}
]
那么,有人可以帮忙解决这个问题吗?我该如何实现这个目标?
我尝试创建一个[]interface{}
,然后迭代person
以附加每个数据,但是这种方法的问题是,如果Data
是一个空字符串,它会将其变为null
。我希望它仍然是一个空映射。
英文:
Let's say I have the first struct as
type Person struct {
Name string `json:"person_name"`
Age int `json:"person_age"`
Data map[string]interface{} `json:"data"`
}
and I am trying to marshal an array of the above struct
Things work well till here and a sample response I receive is
[
{
"person_name":"name",
"person_age":12,"data":{}
},
{
"person_name":"name2",
"person_age":12,"data":{}
}
]
Now, I need to append another struct over here and the final response should like
[
{
"person_name":"name",
"person_age":12,"data":{}
},
{
"person_name":"name2",
"person_age":12,"data":{}
},
{
"newData":"value"
}
]
So can someone help on this and how i can achieve this ?
I tried by creating an []interface{} and then iterating over person
to append each data, but the issue in this approach is that it makes the Data
as null
if in case it's an empty string.
I would need it be an empty map only.
答案1
得分: 2
让我先说一下,根据我的理解,你可能正在处理一个X-Y问题。我很难想象出很多合理的用例,其中一个已定义的数据类型必须与完全不同的、潜在的任意/自由形式的数据结构一起进行编组。虽然这是可能的,以下是你可以实现它的方法:
所以你只是想将一个完全不同的结构附加到数据集中,然后将其编组并以JSON格式返回结果?你需要为此创建一个新的切片:
personData := []Person{} // 这里是person 1和person 2
more := map[string]string{ // 或者其他某个结构体
"newdata": "value",
}
allData := make([]interface{}, 0, len(personData)+1) // +1 是为了more,根据需要编组的对象数量设置容量
for _, p := range personData {
allData = append(allData, p) // 将其复制到该切片中,因为[]Person与[]interface{}不兼容
}
allData = append(allData, more)
bJSON, err := json.Marshal(allData)
if err != nil {
// 处理错误
}
fmt.Println(string(bJSON))
基本上,因为你试图编组一个包含多种不同类型的切片,所以你必须将所有对象添加到类型为interface{}
的切片中,然后一次性进行编组。
更简洁的方法
有更简洁的方法可以允许你进行数据的_反编组_,前提是已知涉及的不同数据类型。考虑使用包装类型,如下所示:
type Person struct {} // 你已经有的那个
type NewData struct {
NewData string `json:"newdata"`
}
type MixedData struct {
*Person
*NewData
}
在这个MixedData
类型中,Person
和NewData
都被嵌入进来,所以MixedData
本质上将充当所有嵌入类型的合并版本(具有相同名称的字段应该在此级别被覆盖)。使用这种类型,你可以相应地进行编组和反编组JSON:
allData := []MixedData{
{
Person: &person1,
},
{
Person: &person2,
},
{
NewData: &newData,
},
}
类似地,当你有一个JSON []byte
输入时,你可以像处理任何其他类型一样进行反编组:
data := []MixedData{}
if err := json.Unmarshal(in, &data); err != nil {
// 处理错误
}
fmt.Printf("%#v\n", data) // 它会在这里
最好为MixedData
类型添加一些函数/获取器:
func (m MixedData) IsPerson() bool { return m.Person != nil }
func (m MixedData) GetPerson() *Person {
if m.Person == nil {
return nil
}
cpy := *m.Person // 创建一个副本以避免共享指针
return &cpy // 返回指向副本的指针
}
对所有嵌入类型执行相同的操作,这样就可以完美运行了。
如前所述,如果嵌入类型包含具有相同名称的字段,那么你应该在MixedData
类型中覆盖它们。假设你有一个Person
和Address
类型,两者都有一个ID
字段:
type MixedData struct {
ID string `json:"id"`
*Person
*Address
}
这将在MixedData
类型上设置ID
值,并在相应的嵌入结构体上设置所有其他(非共享)字段。然后你可以使用获取器在需要的地方设置ID,或者使用自定义的解组器,但我将把实现留给你。
英文:
Let me prefix this by saying this looks to me very much like you might be dealing with an X-Y problem. I can't really think of many valid use-cases where one would end up with a defined data-type that has to somehow be marshalled alongside a completely different, potentially arbitrary/freeform data structure. It's possible, though, and this is how you could do it:
So you just want to append a completely different struct to the data-set, then marshal it and return the result as JSON? You'll need to create a new slice for that:
personData := []Person{} // person 1 and 2 here
more := map[string]string{ // or some other struct
"newdata": "value",
}
allData := make([]any, 0, len(personData) + 1) // the +1 is for the more, set cap to however many objects you need to marshal
for _, p := range personData {
allData = append(allData, p) // copy over to this slice, because []Person is not compatible with []any
}
allData = append(allData, more)
bJSON, err := json.Marshal(allData)
if err != nil {
// handle
}
fmt.Println(string(bJSON))
Essentially, because you're trying to marshal a slice containing multiple different types, you have to add all objects to a slice of type any
(short for interface{}
) before marshalling it all in one go
Cleaner approaches
There are much, much cleaner approaches that allow you to unmarshal the data, too, assuming the different data-types involved are known beforehand. Consider using a wrapper type like so:
type Person struct {} // the one you have
type NewData {
NewData string `json:"newdata"`
}
type MixedData struct {
*Person
*NewData
}
In this MixedData
type, both Person
and NewData
are embedded, so MixedData
will essentially act as a merged version of all embedded types (fields with the same name should be overridden at this level). With this type, you can marshal and unmarshal the JSON accordingly:
allData := []MixedData{
{
Person: &person1,
},
{
Person: &person2,
},
{
NewData: &newData,
},
}
Similarly, when you have a JSON []byte
input, you can unmarshal it same as you would any other type:
data := []MixedData{}
if err := json.Unmarshal(&data, in); err != nil {
// handle
}
fmt.Printf("%#v\n", data) // it'll be there
It pays to add some functions/getters to the MixedData
type, though:
func (m MixedData) IsPerson() bool { return m.Person != nil }
func (m MixedData) Person() *Person {
if m.Person == nil {
return nil
}
cpy := *m.Person // create a copy to avoid shared pointers
return &cpy // return pointer to the copy
}
Do the same for all embedded types and this works like a charm.
As mentioned before, should your embedded types contain fields with the same name, then you should override them in the MixedData
type. Say you have a Person
and Address
type, and both have an ID
field:
type MixedData struct {
ID string `json:"id"`
*Person
*Address
}
This will set the ID
value on the MixedData
type, and all other (non-shared) fields on the corresponding embedded struct. You can then use the getters to set the ID where needed, or use a custom unmarshaller, but I'll leave that to you to implement
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论