如何动态更改struct的json标记?

时间:2017-03-02 03:50:18

标签: go

我有以下内容:

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},
    )
}

我正在尝试对结构进行json编码,但在此之前我需要更改json键...例如,最终的json应该如下所示:

{"id": 1, "name": "Ken Jennings", "address": "123 Jennings Street"}

我注意到value.Type()。Field(i).Tag.Get(“json”)的方法,但是没有setter方法。为什么?以及如何获得所需的json输出。

另外,如何遍历所有字段,包括嵌入式结构另一个?

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

4 个答案:

答案 0 :(得分:9)

在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"` // 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)
}

会给我们:

{"id":1,"name":"Ken Jennings","address":"123 Jennings Street"}

这个解决方案可以通过以下事实实现:在Go 1.8中,您可以为结构分配相同但结构不同的结构。如您所见,类型aliasUser类型的字段相同,但标记不同。

答案 1 :(得分:5)

这很糟糕,但是如果你可以将结构包装在另一个结构中,并使用新结构进行编码,那么你可以:

  1. 对原始结构进行编码,
  2. 将其解码为interface{}以获取地图
  3. 替换地图密钥
  4. 然后对地图进行编码并将其返回
  5. 因此:

    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)
    }
    

    在操场上:https://play.golang.org/p/TabSga4i17

答案 2 :(得分:1)

您可以使用 reflect.StructOf reflect.Value.Convert 函数
来创建带有新标签名称的结构副本。 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())
}

答案 3 :(得分:0)

问题中似乎类型为 User tag 属性旨在用于为 Name 属性重命名JSON字段名。

具有一点反射的MarshalJSON实现可以完成这项工作,而无需附加的 tag 属性,也无需附加的包装器结构(如已接受的答案所建议),如下所示:

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)
}

这将打印:

{"Another":{"address":"123 Jennings Street"},"id":1,"name":"Ken Jennings"}

但是,请注意,MarshalJSON始终对JSON标签进行排序,而标准编码器保留了结构字段的顺序。