在Go语言中,如何将同一个结构体在JSON编组/解组时转换为不同的JSON格式?

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

JSON marshalling/unmarshalling same struct to different JSON format in go?

问题

我有一个结构体,我想根据上下文的不同以不同的方式将其编组为JSON。

例如,有时我想这样编组:

type MyStruct struct {
    Nickname       string `json:"nickname"`
    EmailAddress   string `json:"email_address"`
    PhoneNumber    string `json:"-"`
    MailingAddress string `json:"-"`
}

有时我想这样编组:

type MyStruct struct {
    Nickname       string `json:"nickname"`
    EmailAddress   string `json:"email_address"`
    PhoneNumber    string `json:"phone_number"`
    MailingAddress string `json:"mailing_address"`
}

有没有简单的方法可以做到这一点,而不需要:

  1. 创建两个单独的结构体。
  2. 编写自定义的编组器。
  3. 暂时删除 PhoneNumber 和 MailingAddress 的字符串值(在标签上使用 omitempty),进行编组,然后再添加回来。

如果有一种方法可以:

  1. 指定两组标签,并告诉编组器使用哪组标签。
  2. 在运行时动态更改标签。
英文:

I have a struct that I'd like to Marshal into JSON differently depending on the context.

For example, sometimes I want to marshal like this:

    type MyStruct struct {
        Nickname       string `json:"nickname"`
        EmailAddress   string `json:"email_address"`
        PhoneNumber    string `json:"-"`
        MailingAddress string `json:"-"`
    }

And sometimes I want to marshal like this:

    type MyStruct struct {
        Nickname       string `json:"nickname"`
        EmailAddress   string `json:"email_address"`
        PhoneNumber    string `json:"phone_number"`
        MailingAddress string `json:"mailing_address"`
    }

Is there a simple way to do this without:

  1. Making 2 separate structs.
  2. Writing a custom marshaller.
  3. Temporarily removing the string values for PhoneNumber and MailingAddress (with an omitempty on the tag), marshaling and then adding them back.

If only there was a way to:

  1. Specify 2 sets of tags and tell the marshaler which ones to use.
  2. Dynamically change the tags at runtime.

答案1

得分: 17

我知道你明确提到“不使用自定义编组器”,但是如果有人看到这个并认为应该避免使用自定义编组器,因为它太复杂,那么编写一个自定义编组器来实现你想要的功能其实非常简单:

type MyStruct struct {
    Nickname       string `json:"nickname"`
    EmailAddress   string `json:"email_address"`
    PhoneNumber    string `json:"phone_number"`
    MailingAddress string `json:"mailing_address"`
    all            bool
}

func (ms MyStruct) MarshalJSON() ([]byte, error) {
    m := map[string]interface{}{} // 最好使用正确容量的 make 函数
    m["nickname"] = ms.Nickname
    m["email_address"] = ms.EmailAddress
    if ms.all {
        m["phone_number"] = ms.PhoneNumber
        m["mailing_address"] = ms.MailingAddress
    }
    return json.Marshal(m)
}

如果all字段应该由外部包设置,那么可以在结构体上定义一个方法,或者将该字段设置为公开的(不会影响 JSON,因为它是通过自定义编组器进行编码的)。

在 playground 上有一个可运行的示例:http://play.golang.org/p/1N_iBzvuW4

英文:

I know you explicitly mention "without writing a custom marshaler", but in case someone sees this and thinks it should be avoided because of complexity, a custom marshaler to do what you want to do is really simple:

type MyStruct struct {
	Nickname       string `json:"nickname"`
	EmailAddress   string `json:"email_address"`
	PhoneNumber    string `json:"phone_number"`
	MailingAddress string `json:"mailing_address"`
	all            bool
}

func (ms MyStruct) MarshalJSON() ([]byte, error) {
	m := map[string]interface{}{} // ideally use make with the right capacity
	m["nickname"] = ms.Nickname
	m["email_address"] = ms.EmailAddress
	if ms.all {
		m["phone_number"] = ms.PhoneNumber
		m["mailing_address"] = ms.MailingAddress
	}
	return json.Marshal(m)
}

If the all field should be set by an external package, then a method could be defined on the struct, or the field could be made public (wouldn't affect the JSON since it is encoded via the custom marshaler).

Runnable example on the playground: http://play.golang.org/p/1N_iBzvuW4

答案2

得分: 0

你可以使用反射(reflection),虽然不是最高效的解决方案,但很方便。

func MarshalSubset(obj interface{}, fields ...string) ([]byte, error) {
    if len(fields) == 0 {
        return json.Marshal(obj)
    }
    out := make(map[string]interface{}, len(fields))
    val := reflect.ValueOf(obj)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    if val.Kind() != reflect.Struct {
        panic("not a struct")
    }
    typ := val.Type()
    for _, f := range fields {
        val := val.FieldByName(f).Interface()
        rfld, _ := typ.FieldByName(f)
        tag := strings.Split(rfld.Tag.Get("json"), ",")
        if len(tag) > 0 {
            f = tag[0]
        }
        out[f] = val
    }

    return json.Marshal(out)
}

[kbd] [playground](http://play.golang.org/p/pu7vHz4kPZ) [/kbd]
英文:

You can use reflection, not really the most efficient solution but it is convenient.

func MarshalSubset(obj interface{}, fields ...string) ([]byte, error) {
	if len(fields) == 0 {
		return json.Marshal(obj)
	}
	out := make(map[string]interface{}, len(fields))
	val := reflect.ValueOf(obj)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	if val.Kind() != reflect.Struct {
		panic("not a struct")
	}
	typ := val.Type()
	for _, f := range fields {
		val := val.FieldByName(f).Interface()
		rfld, _ := typ.FieldByName(f)
		tag := strings.Split(rfld.Tag.Get("json"), ",")
		if len(tag) > 0 {
			f = tag[0]
		}
		out[f] = val
	}

	return json.Marshal(out)
}

<kbd>playground</kbd>

huangapple
  • 本文由 发表于 2014年10月11日 00:11:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/26303694.html
匿名

发表评论

匿名网友

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

确定