如何动态更改结构体的 JSON 标签?

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

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 (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;reflect&quot;
)

type User struct {
	ID   int64  `json:&quot;id&quot;`
	Name string `json:&quot;first&quot;` // want to change this to `json:&quot;name&quot;`
	tag  string `json:&quot;-&quot;`
	Another
}

type Another struct {
	Address string `json:&quot;address&quot;`
}

func (u *User) MarshalJSON() ([]byte, error) {
	value := reflect.ValueOf(*u)
	for i := 0; i &lt; value.NumField(); i++ {
		tag := value.Type().Field(i).Tag.Get(&quot;json&quot;)
		field := value.Field(i)
		fmt.Println(tag, field)
	}
	return json.Marshal(u)
}

func main() {
        anoth := Another{&quot;123 Jennings Street&quot;}
	_ = json.NewEncoder(os.Stdout).Encode(
		&amp;User{1, &quot;Ken Jennings&quot;, &quot;name&quot;,
		     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:

{&quot;id&quot;: 1, &quot;name&quot;: &quot;Ken Jennings&quot;, &quot;address&quot;: &quot;123 Jennings Street&quot;}

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?

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

答案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{&quot;123 Jennings Street&quot;}

	_ = json.NewEncoder(os.Stdout).Encode(
		&amp;User{1, &quot;Ken Jennings&quot;, &quot;name&quot;,
			anoth},
	)
}

type User struct {
	ID   int64  `json:&quot;id&quot;`
	Name string `json:&quot;first&quot;` // want to change this to `json:&quot;name&quot;`
	tag  string `json:&quot;-&quot;`
	Another
}

type Another struct {
	Address string `json:&quot;address&quot;`
}

func (u *User) MarshalJSON() ([]byte, error) {
	type alias struct {
		ID   int64  `json:&quot;id&quot;`
		Name string `json:&quot;name&quot;`
		tag  string `json:&quot;-&quot;`
		Another
	}
	var a alias = alias(*u)
	return json.Marshal(&amp;a)
}

Will give us:

{&quot;id&quot;:1,&quot;name&quot;:&quot;Ken Jennings&quot;,&quot;address&quot;:&quot;123 Jennings Street&quot;}

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

这是一个笨拙的方法,但如果你能将结构体包装在另一个结构体中,并且在编码时使用新的结构体,那么你可以按照以下步骤进行操作:

  1. 编码原始结构体,
  2. 将其解码为interface{}以获取一个map,
  3. 替换map的键,
  4. 然后编码该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:

  1. Encode the original struct,
  2. Decode it to an interface{} to get a map
  3. Replace the map key
  4. 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, &amp;a)
	b := a.(map[string]interface{})

	// Replace the map key
	b[&quot;name&quot;] = b[&quot;first&quot;]
	delete(b, &quot;first&quot;)

	// Return encoding of the map
	return json.Marshal(b)
}

In the playground: https://play.golang.org/p/TabSga4i17

答案3

得分: 8

你可以使用reflect.StructOfreflect.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 &lt; t.NumField(); i++ {
		fmt.Println(t.Field(i).Tag)
		sf = append(sf, t.Field(i))
		if t.Field(i).Name == &quot;Name&quot; {
			sf[i].Tag = `json:&quot;name&quot;`
		}
	}
	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 (
&quot;encoding/json&quot;
&quot;os&quot;
&quot;reflect&quot;
)
type User struct {
ID   int64  `json:&quot;id&quot;`
Name string `json:&quot;first&quot;` // want to change this to `json:&quot;name&quot;`
Another
}
type Another struct {
Address string `json:&quot;address&quot;`
}
// define the naming strategy
func (User) SetJSONname(jsonTag string) string {
if jsonTag == &quot;first&quot;{
return &quot;name&quot;
}
return jsonTag
}
// implement MarshalJSON for type User
func (u User) MarshalJSON() ([]byte, error) {
// specify the naming strategy here
return marshalJSON(&quot;SetJSONname&quot;, 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 := &quot;&quot;
for i := 0; i &lt; t.NumField(); i++ {
f := t.Field(i)
switch n := f.Tag.Get(&quot;json&quot;); n {
case &quot;&quot;:
outName = f.Name
case &quot;-&quot;:
outName = &quot;&quot;
default:
outName = fname(n)
}
if outName != &quot;&quot; {
out[outName] = v.Field(i).Interface()
}
}
return json.Marshal(out)
}
func main() {
anoth := Another{&quot;123 Jennings Street&quot;}
u := User{1, &quot;Ken Jennings&quot;, anoth,}
e := json.NewEncoder(os.Stdout)
e.Encode(u)
}

This will print:

{&quot;Another&quot;:{&quot;address&quot;:&quot;123 Jennings Street&quot;},&quot;id&quot;:1,&quot;name&quot;:&quot;Ken Jennings&quot;}

However, be aware that MarshalJSON always sorts the JSON tags, while the standard encoder preserves the order of struct fields.

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

发表评论

匿名网友

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

确定