22

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?

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

  • tags aren't a field that can be set, they are part of the type. – JimB Mar 02 '17 at 03:56
  • maybe there's no clean solution for this. and one of the dirty one is using code generation. – ymonad Mar 02 '17 at 03:59
  • 3
    Implement the json.Marshaler (https://golang.org/pkg/encoding/json/#Marshaler) interface and you can ignore struct tags or name your fields whatever you like. – elithrar Mar 02 '17 at 04:15
  • I did. What do you mean? –  Mar 02 '17 at 04:31
  • 2
    In Go 1.8 you can declare a second type with identical fields and re-assign (copy) a value of the first type to the second type (with different tags), e.g. https://play.golang.org/p/QeT1s96p1R – az_ Mar 02 '17 at 04:40
  • The problem is that I need to be able to set the field dynamically. It may be "name" but may not and could be "last", "something else", etc. And there are many, many possibilities for what it could be...I wish I knew beforehand what the fields were but they are dynamically set. –  Mar 02 '17 at 04:45
  • 1
    Then you will probably need to implement a Marshaler as suggested above, e.g. https://play.golang.org/p/qXRYNSukaW – az_ Mar 02 '17 at 05:00
  • this works for me: http://choly.ca/post/go-json-marshalling/ (also this helped https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/) – Victor May 13 '21 at 23:06

4 Answers4

21

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.

Kaveh Shahbazian
  • 13,088
  • 13
  • 80
  • 139
  • 13
    This isn't dynamic. This is still statically writing structs. – Collin Bell May 08 '18 at 11:21
  • 1
    In the question, the `json.Marshaler` interface is being implemented by implementing `MarshalJSON` method, which means the type is known at the time of coding, in this case `MyUser`. And the solution is an idiomatic piece of Go which I did not invent. The intention of the title of the question differs from the intention conveyed inside the body. And this is not marked as the answer anyway. – Kaveh Shahbazian May 08 '18 at 13:01
17

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, &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

muru
  • 4,723
  • 1
  • 34
  • 78
8

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())
}
Chris Cheng
  • 81
  • 1
  • 3
3

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.

Don P
  • 173
  • 11