JSON omitempty With time.Time Field

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

JSON omitempty With time.Time Field

问题

尝试将包含两个时间字段的结构体进行JSON编组(Marshal)。但是,我只希望在时间字段具有值时才传递该字段。因此,我使用了json:",omitempty",但它没有起作用。

我应该将Date值设置为什么,以便json.Marshal将其视为空(零)值并将其排除在JSON字符串之外?

Playground链接:http://play.golang.org/p/QJwh7yBJlo

实际结果:

{"Timestamp":"2015-09-18T00:00:00Z","Date":"0001-01-01T00:00:00Z"}

期望结果:

{"Timestamp":"2015-09-18T00:00:00Z"}

代码:

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type MyStruct struct {
	Timestamp time.Time `json:",omitempty"`
	Date      time.Time `json:",omitempty"`
	Field     string    `json:",omitempty"`
}

func main() {
	ms := MyStruct{
		Timestamp: time.Date(2015, 9, 18, 0, 0, 0, 0, time.UTC),
		Field:     "",
	}

	bb, err := json.Marshal(ms)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(bb))
}
英文:

Trying to json Marshal a struct that contains 2 time fields. But I only want the field to come through if it has a time value. So I'm using json:",omitempty" but it's not working.

What can I set the Date value to so json.Marshal will treat it like an empty (zero) value and not include it in the json string?

Playground: http://play.golang.org/p/QJwh7yBJlo

Actual Outcome:

> {"Timestamp":"2015-09-18T00:00:00Z","Date":"0001-01-01T00:00:00Z"}

Desired Outcome:

> {"Timestamp":"2015-09-18T00:00:00Z"}

Code:

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type MyStruct struct {
	Timestamp time.Time `json:",omitempty"`
	Date      time.Time `json:",omitempty"`
	Field     string    `json:",omitempty"`
}

func main() {
	ms := MyStruct{
		Timestamp: time.Date(2015, 9, 18, 0, 0, 0, 0, time.UTC),
		Field:     "",
	}

	bb, err := json.Marshal(ms)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(bb))
}

答案1

得分: 145

omitempty标签选项在处理time.Time时不起作用,因为它是一个结构体。结构体有一个“零”值,但这是一个结构体值,其中所有字段都具有它们的零值。这是一个“有效”的值,因此不被视为“空”。

但是,通过将其更改为指针:*time.Time,它将起作用(nil指针在json编组/解组时被视为“空”)。因此,在这种情况下不需要编写自定义的Marshaler

type MyStruct struct {
    Timestamp *time.Time `json:",omitempty"`
    Date      *time.Time `json:",omitempty"`
    Field     string     `json:",omitempty"`
}

使用它:

ts := time.Date(2015, 9, 18, 0, 0, 0, 0, time.UTC)
ms := MyStruct{
    Timestamp: &ts,
    Field:     "",
}

输出(如所需):

{"Timestamp":"2015-09-18T00:00:00Z"}

Go Playground上尝试一下。

如果您不能或不想将其更改为指针,仍然可以通过实现自定义的MarshalerUnmarshaler来实现您想要的效果。如果这样做,您可以使用Time.IsZero()方法来判断time.Time值是否为零值。

英文:

The omitempty tag option does not work with time.Time as it is a struct. There is a "zero" value for structs, but that is a struct value where all fields have their zero values. This is a "valid" value, so it is not treated as "empty".

But by simply changing it to a pointer: *time.Time, it will work (nil pointers are treated as "empty" for json marshaling/unmarshaling). So no need to write custom Marshaler in this case:

type MyStruct struct {
	Timestamp *time.Time `json:",omitempty"`
	Date      *time.Time `json:",omitempty"`
	Field     string     `json:",omitempty"`
}

Using it:

ts := time.Date(2015, 9, 18, 0, 0, 0, 0, time.UTC)
ms := MyStruct{
	Timestamp: &ts,
	Field:     "",
}

Output (as desired):

{"Timestamp":"2015-09-18T00:00:00Z"}

Try it on the Go Playground.

If you can't or don't want to change it to a pointer, you can still achieve what you want by implementing a custom Marshaler and Unmarshaler. If you do so, you can use the Time.IsZero() method to decide if a time.Time value is the zero value.

答案2

得分: 8

你可以为自定义的编组格式定义自己的Time类型,并在所有地方使用它,而不是使用time.Time

以下是代码的翻译:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

type MyTime struct {
	*time.Time
}

func (t MyTime) MarshalJSON() ([]byte, error) {
	return []byte(t.Format("\"" + time.RFC3339 + "\"")), nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// The time is expected to be a quoted string in RFC 3339 format.
func (t *MyTime) UnmarshalJSON(data []byte) (err error) {

	// by convention, unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
	if bytes.Equal(data, []byte("null")) {
		return nil
	}

	// Fractional seconds are handled implicitly by Parse.
	tt, err := time.Parse("\""+time.RFC3339+"\"", string(data))
	*t = MyTime{&tt}
	return
}

func main() {
	t := time.Now()
	d, err := json.Marshal(MyTime{&t})
	fmt.Println(string(d), err)
	var mt MyTime
	json.Unmarshal(d, &mt)
	fmt.Println(mt)
}

希望对你有帮助!

英文:

You may define you self Time type for custom marshal format, and use it everywhere instead time.Time

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

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

type MyTime struct {
	*time.Time
}

func (t MyTime) MarshalJSON() ([]byte, error) {
	return []byte(t.Format("\"" + time.RFC3339 + "\"")), nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// The time is expected to be a quoted string in RFC 3339 format.
func (t *MyTime) UnmarshalJSON(data []byte) (err error) {

	// by convention, unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
	if bytes.Equal(data, []byte("null")) {
		return nil
	}

	// Fractional seconds are handled implicitly by Parse.
	tt, err := time.Parse("\""+time.RFC3339+"\"", string(data))
	*t = MyTime{&tt}
	return
}

func main() {
	t := time.Now()
	d, err := json.Marshal(MyTime{&t})
	fmt.Println(string(d), err)
	var mt MyTime
	json.Unmarshal(d, &mt)
	fmt.Println(mt)
}

答案3

得分: 4

作为对icza答案的跟进,这是一个自定义的编组程序,它省略了一个空日期字段,但保持其他字段不变。

func (ms *MyStruct) MarshalJSON() ([]byte, error) {
    type Alias MyStruct
    if ms.Timestamp.IsZero() {
        return json.Marshal(&struct {
            Timestamp int64 `json:",omitempty"`
            *Alias
        }{
            Timestamp: 0,
            Alias:     (*Alias)(ms),
        })
    } else {
        return json.Marshal(&struct {
            *Alias
        }{
            Alias: (*Alias)(ms),
        })
    }
}

这是从http://choly.ca/post/go-json-marshalling/借鉴的。

原始问题有两个时间字段,这会使问题变得更加复杂(你需要检查两个字段都为空、其中一个为空还是都不为空!)。

可能有更好的方法来实现这一点,所以欢迎评论。

英文:

As a follow up to icza's answer here is a custom marshaller that omits an empty date field but keeps the rest of the fields unchanged.

func (ms *MyStruct) MarshalJSON() ([]byte, error) {
	type Alias MyStruct
	if ms.Timestamp.IsZero() {
		return json.Marshal(&struct {
			Timestamp int64 `json:",omitempty"`
			*Alias
		}{
			Timestamp: 0,
			Alias:     (*Alias)(ms),
		})
	} else {
		return json.Marshal(&struct {
			*Alias
		}{
			Alias: (*Alias)(ms),
		})
	}
}

This borrows from http://choly.ca/post/go-json-marshalling/

The OPs case has two time fields which would make it much more complicated. (you'd have to check for neither, either and both being empty!)

There may be better ways to achieve this, so comments are welcome.

huangapple
  • 本文由 发表于 2015年9月18日 12:20:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/32643815.html
匿名

发表评论

匿名网友

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

确定