英文:
How do I dynamically change the struct's json tag?
问题
我有以下代码:
package main
import (
"encoding/json"
"fmt"
"os"
"reflect"
)
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // 想要将此字段更改为 `json:"name"`
tag string `json:"-"`
Another
}
type Another struct {
Address string `json:"address"`
}
func (u *User) MarshalJSON() ([]byte, error) {
value := reflect.ValueOf(*u)
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag.Get("json")
field := value.Field(i)
fmt.Println(tag, field)
}
return json.Marshal(u)
}
func main() {
anoth := Another{"123 Jennings Street"}
_ = json.NewEncoder(os.Stdout).Encode(
&User{1, "Ken Jennings", "name",
anoth},
)
}
我正在尝试对结构体进行 JSON 编码,但在此之前,我需要更改 JSON 键。例如,最终的 JSON 应该如下所示:
{"id": 1, "name": "Ken Jennings", "address": "123 Jennings Street"}
我注意到了 value.Type().Field(i).Tag.Get("json")
这个方法,但是没有设置方法。为什么?我该如何获得所需的 JSON 输出?
另外,我该如何遍历所有字段,包括嵌入的结构体 Another
?
链接:https://play.golang.org/p/Qi8Jq_4W0t
英文:
I have the following:
package main
import (
"encoding/json"
"fmt"
"os"
"reflect"
)
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // want to change this to `json:"name"`
tag string `json:"-"`
Another
}
type Another struct {
Address string `json:"address"`
}
func (u *User) MarshalJSON() ([]byte, error) {
value := reflect.ValueOf(*u)
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag.Get("json")
field := value.Field(i)
fmt.Println(tag, field)
}
return json.Marshal(u)
}
func main() {
anoth := Another{"123 Jennings Street"}
_ = json.NewEncoder(os.Stdout).Encode(
&User{1, "Ken Jennings", "name",
anoth},
)
}
I am trying to json encode the struct but before I do I need to change the json key...eg the final json should look like:
{"id": 1, "name": "Ken Jennings", "address": "123 Jennings Street"}
I noticed the method for value.Type().Field(i).Tag.Get("json"), however there is no setter method. Why? and how do I get the desired json output.
Also, how do I iterate through all the fields, including the embedded struct Another?
答案1
得分: 21
自Go 1.8版本以来,您可以使用以下解决方案:
func main() {
anoth := Another{"123 Jennings Street"}
_ = json.NewEncoder(os.Stdout).Encode(
&User{1, "Ken Jennings", "name",
anoth},
)
}
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // 想要将此字段更改为 `json:"name"`
tag string `json:"-"`
Another
}
type Another struct {
Address string `json:"address"`
}
func (u *User) MarshalJSON() ([]byte, error) {
type alias struct {
ID int64 `json:"id"`
Name string `json:"name"`
tag string `json:"-"`
Another
}
var a alias = alias(*u)
return json.Marshal(&a)
}
将得到以下结果:
{"id":1,"name":"Ken Jennings","address":"123 Jennings Street"}
这个解决方案的实现得益于Go 1.8版本中的一个特性,即可以将具有相同结构但不同标签的结构体相互赋值。正如您所看到的,类型alias
具有与类型User
相同的字段,但具有不同的标签。
英文:
Since Go 1.8 you can use this solution:
func main() {
anoth := Another{"123 Jennings Street"}
_ = json.NewEncoder(os.Stdout).Encode(
&User{1, "Ken Jennings", "name",
anoth},
)
}
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // want to change this to `json:"name"`
tag string `json:"-"`
Another
}
type Another struct {
Address string `json:"address"`
}
func (u *User) MarshalJSON() ([]byte, error) {
type alias struct {
ID int64 `json:"id"`
Name string `json:"name"`
tag string `json:"-"`
Another
}
var a alias = alias(*u)
return json.Marshal(&a)
}
Will give us:
{"id":1,"name":"Ken Jennings","address":"123 Jennings Street"}
This solution made possible by the fact that in Go 1.8 you can assign structs with same structure but different tags to each other. As you see type alias
has the same fields as type User
but with different tags.
答案2
得分: 17
这是一个笨拙的方法,但如果你能将结构体包装在另一个结构体中,并且在编码时使用新的结构体,那么你可以按照以下步骤进行操作:
- 编码原始结构体,
- 将其解码为
interface{}
以获取一个map, - 替换map的键,
- 然后编码该map并返回。
因此:
type MyUser struct {
U User
}
func (u MyUser) MarshalJSON() ([]byte, error) {
// 编码原始结构体
m, _ := json.Marshal(u.U)
// 解码为map
var a interface{}
json.Unmarshal(m, &a)
b := a.(map[string]interface{})
// 替换map的键
b["name"] = b["first"]
delete(b, "first")
// 返回编码后的map
return json.Marshal(b)
}
在playground上的示例:https://play.golang.org/p/TabSga4i17
英文:
It's kludgy, but if you can wrap the struct in another, and use the new one for encoding, then you could:
- Encode the original struct,
- Decode it to an
interface{}
to get a map - Replace the map key
- Then encode the map and return it
Thus:
type MyUser struct {
U User
}
func (u MyUser) MarshalJSON() ([]byte, error) {
// encode the original
m, _ := json.Marshal(u.U)
// decode it back to get a map
var a interface{}
json.Unmarshal(m, &a)
b := a.(map[string]interface{})
// Replace the map key
b["name"] = b["first"]
delete(b, "first")
// Return encoding of the map
return json.Marshal(b)
}
In the playground: https://play.golang.org/p/TabSga4i17
答案3
得分: 8
你可以使用reflect.StructOf和reflect.Value.Convert函数创建一个具有新标签名称的结构体副本。
func (u *User) MarshalJSON() ([]byte, error) {
value := reflect.ValueOf(*u)
t := value.Type()
sf := make([]reflect.StructField, 0)
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Tag)
sf = append(sf, t.Field(i))
if t.Field(i).Name == "Name" {
sf[i].Tag = `json:"name"`
}
}
newType := reflect.StructOf(sf)
newValue := value.Convert(newType)
return json.Marshal(newValue.Interface())
}
你可以在这里查看示例代码:https://play.golang.org/p/zJ2GLreYpl0
英文:
You can create a struct copy w/ new tag name by using reflect.StructOf and reflect.Value.Convert function
https://play.golang.org/p/zJ2GLreYpl0
func (u *User) MarshalJSON() ([]byte, error) {
value := reflect.ValueOf(*u)
t := value.Type()
sf := make([]reflect.StructField, 0)
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Tag)
sf = append(sf, t.Field(i))
if t.Field(i).Name == "Name" {
sf[i].Tag = `json:"name"`
}
}
newType := reflect.StructOf(sf)
newValue := value.Convert(newType)
return json.Marshal(newValue.Interface())
}
答案4
得分: 3
似乎问题中的类型为"User"的"tag"属性是用于重命名"Name"属性的JSON字段名。
使用一些反射的实现可以完成这个任务,而不需要额外的"tag"属性,也不需要额外的包装结构体(如接受的答案中所建议的),代码如下:
package main
import (
"encoding/json"
"os"
"reflect"
)
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // 想要将其更改为 `json:"name"`
Another
}
type Another struct {
Address string `json:"address"`
}
// 定义命名策略
func (User) SetJSONname(jsonTag string) string {
if jsonTag == "first" {
return "name"
}
return jsonTag
}
// 为类型User实现MarshalJSON
func (u User) MarshalJSON() ([]byte, error) {
// 在这里指定命名策略
return marshalJSON("SetJSONname", u)
}
// 实现一个通用的编组器,接受一个命名策略
func marshalJSON(namingStrategy string, that interface{}) ([]byte, error) {
out := map[string]interface{}{}
t := reflect.TypeOf(that)
v := reflect.ValueOf(that)
fnctn := v.MethodByName(namingStrategy)
fname := func(params ...interface{}) string {
in := make([]reflect.Value, len(params))
for k, param := range params {
in[k] = reflect.ValueOf(param)
}
return fnctn.Call(in)[0].String()
}
outName := ""
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
switch n := f.Tag.Get("json"); n {
case "":
outName = f.Name
case "-":
outName = ""
default:
outName = fname(n)
}
if outName != "" {
out[outName] = v.Field(i).Interface()
}
}
return json.Marshal(out)
}
func main() {
anoth := Another{"123 Jennings Street"}
u := User{1, "Ken Jennings", anoth}
e := json.NewEncoder(os.Stdout)
e.Encode(u)
}
这将打印出:
{"Another":{"address":"123 Jennings Street"},"id":1,"name":"Ken Jennings"}
但是,请注意,MarshalJSON 总是对 JSON 标签进行排序,而标准编码器会保留结构字段的顺序。
英文:
It seems the tag property of type User in the question is meant to be used for renaming the JSON fieldname for the Name property.
An implementation of MarshalJSON with a bit of reflection can do the job without that additional tag property and also without an additional wrapper struct (as suggested in the accepted answer), like so:
package main
import (
"encoding/json"
"os"
"reflect"
)
type User struct {
ID int64 `json:"id"`
Name string `json:"first"` // want to change this to `json:"name"`
Another
}
type Another struct {
Address string `json:"address"`
}
// define the naming strategy
func (User) SetJSONname(jsonTag string) string {
if jsonTag == "first"{
return "name"
}
return jsonTag
}
// implement MarshalJSON for type User
func (u User) MarshalJSON() ([]byte, error) {
// specify the naming strategy here
return marshalJSON("SetJSONname", u)
}
// implement a general marshaler that takes a naming strategy
func marshalJSON(namingStrategy string, that interface{}) ([]byte, error) {
out := map[string]interface{}{}
t := reflect.TypeOf(that)
v := reflect.ValueOf(that)
fnctn := v.MethodByName(namingStrategy)
fname := func(params ...interface{}) string {
in := make([]reflect.Value, len(params))
for k, param := range params {
in[k] = reflect.ValueOf(param)
}
return fnctn.Call(in)[0].String()
}
outName := ""
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
switch n := f.Tag.Get("json"); n {
case "":
outName = f.Name
case "-":
outName = ""
default:
outName = fname(n)
}
if outName != "" {
out[outName] = v.Field(i).Interface()
}
}
return json.Marshal(out)
}
func main() {
anoth := Another{"123 Jennings Street"}
u := User{1, "Ken Jennings", anoth,}
e := json.NewEncoder(os.Stdout)
e.Encode(u)
}
This will print:
{"Another":{"address":"123 Jennings Street"},"id":1,"name":"Ken Jennings"}
However, be aware that MarshalJSON always sorts the JSON tags, while the standard encoder preserves the order of struct fields.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论