Multiple custom time formats in go for JSON Marshall

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

Multiple custom time formats in go for JSON Marshall

问题

我有一个用于在Go中编组和解组自定义时间格式的实用函数,对于一个格式字符串它可以正常工作,并且在JSON模型中使用如下:

type Entry struct {
    ID                int              `json:"id"`
    AuthorisedBy      string           `json:"authorisedBy"`
    Duid              string           `json:"duid"`
    IntervalCount     int32            `json:"intervalCount"`
    Intervals         []int32          `json:"intervals"`
    RequestId         string           `json:"requestId"`
    RequestTimestamp  time.Time        `json:"requestTimestamp"`
    TradingDate       utils.CustomTime `json:"tradingDate"`
    Unit              string           `json:"unit"`
    RebidExplanation  string           `json:"rebidexplanation"`
    AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
}

实用函数如下:

package utils

import (
    "database/sql/driver"
    "fmt"
    "strings"
    "time"
)

type CustomTime struct {
    time.Time
}

const ctLayout = "2006-01-02"

func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) {
    s := strings.Trim(string(b), "\"")
    if s == "null" {
        ct.Time = time.Time{}
        return
    }
    ct.Time, err = time.Parse(ctLayout, s)
    return
}

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ctLayout))), nil
}

var nilTime = (time.Time{}).UnixNano()

func (ct *CustomTime) IsSet() bool {
    return ct.UnixNano() != nilTime
}

func (c CustomTime) Value() (driver.Value, error) {
    return driver.Value(c.Time), nil
}

func (c *CustomTime) Scan(src interface{}) error {
    switch t := src.(type) {
    case time.Time:
        c.Time = t
        return nil
    default:
        return fmt.Errorf("column type not supported")
    }
}

你的问题是,现在我需要将另一个字段格式化为精确到毫秒的时间,例如ctLayout = "2006-01-02 15:04:05.000"。我的问题是,如何设置这个,以便我不需要复制整个结构,是否有一种方法可以在Entry结构中传递ctLayout字符串,并在实用函数中使用它。我相信有办法,但我无法弄清楚如何做。

如果我需要创建一个CustomTimeMS类,那就这样吧,但这似乎是很多重复的代码。

谢谢。

英文:

I have a utility function for marshalling and unmarshalling custom time formats in go that is working fine for one format string and is used in a JSON model as follows

    type Entry struct {
    	ID                int              `json:"id"`
    	AuthorisedBy      string           `json:"authorisedBy"`
    	Duid              string           `json:"duid"`
    	IntervalCount     int32            `json:"intervalCount"`
    	Intervals         []int32          `json:"intervals"`
    	RequestId         string           `json:"requestId"`
    	RequestTimestamp  time.Time        `json:"requestTimestamp"`
    	TradingDate       utils.CustomTime `json:"tradingDate"`
    	Unit              string           `json:"unit"`
    	RebidExplanation  string           `json:"rebidexplanation"`
    	AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
}

The utility function being.

package utils

import (
	"database/sql/driver"
	"fmt"
	"strings"
	"time"
)

type CustomTime struct {
	time.Time,
}

const ctLayout = "2006-01-02"

func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) {
	s := strings.Trim(string(b), "\"")
	if s == "null" {
		ct.Time = time.Time{}
		return
	}
	ct.Time, err = time.Parse(ctLayout, s)
	return
}

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
	if ct.Time.UnixNano() == nilTime {
		return []byte("null"), nil
	}
	return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ctLayout))), nil
}

var nilTime = (time.Time{}).UnixNano()

func (ct *CustomTime) IsSet() bool {
	return ct.UnixNano() != nilTime
}

func (c CustomTime) Value() (driver.Value, error) {
	return driver.Value(c.Time), nil
}

func (c *CustomTime) Scan(src interface{}) error {
	switch t := src.(type) {
	case time.Time:
		c.Time = t
		return nil
	default:
		return fmt.Errorf("column type not supported")
	}
}

My issues, is that I now need to format one of the other fields with exactly 3ms fields such as ctLayout = "2006-01-02 15:04:05.000".

My question is, how do I set this up so I don't need to duplicate the entire structure, Is there a way I can pass in the ctLayout string in the Entry structure and pick it up in the utility function. I'm sure there is, but I can't for the life of me work out how.

If I need to create a CustomTimeMS class, then so be it, but it seems like a lot of duplicate code.

Thanks.

答案1

得分: 2

两种方法:

方法一

在结构体Entry中添加一个导出字段CTLayout

type Entry struct {
    ID                int              `json:"id"`
    AuthorisedBy      string           `json:"authorisedBy"`
    ...
    ...
    AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
    CTLayout          string           `json:"-"`
}

更新MarshalJSON函数

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ct.CTLayout))), nil
}

由于CTLayout是导出的,它可以从任何地方访问。

在调用json.Marshal之前,更新CTLayout为所需的值。

====================================

方法二

添加一个未导出的字段

type Entry struct {
    ID                int              `json:"id"`
    AuthorisedBy      string           `json:"authorisedBy"`
    ...
    ...
    AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
    ctLayout          string
}

更新MarshalJSON函数

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ct.ctLayout))), nil
}

创建一个导出的辅助函数

func (ct *CustomTime) Marshal(ctLayout string) ([]byte, error) {
    // 在这里可以进行其他操作
    // 我们将执行明显的操作
    ct.ctLayout = ctLayout
    return ct.MarshalJSON()
}

调用ct.Marshal(你的布局)

如果你可以访问包含CustomTime的包,可以直接修改CustomTime实例,并将Marshal函数设为未导出。

英文:

Two ways:

One

Add an exported field CTLayout to struct Entry

type Entry struct {
        ID                int              `json:"id"`
        AuthorisedBy      string           `json:"authorisedBy"`
        ...
        ...
        AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
        CTLayout          string           `json:"-"`
}

Update the MarshalJSON function

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ct.CTLayout))), nil
}

Since CTLayout is exported it's accessible from anywhere.

Update CTLayout with what you need before calling json.Marshal on the CustomTime value

====================================

Two

Add an unexported field

type Entry struct {
        ID                int              `json:"id"`
        AuthorisedBy      string           `json:"authorisedBy"`
        ...
        ...
        AcceptedTimestamp time.Time        `json:"acceptedTimestamp"`
        ctLayout          string
}

update the MarshalJSON function

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ct.ctLayout))), nil
}

create a exported helper marshal function

func (ct *CustomTime) Marshal(ctLayout string) ([]byte, error) {
        // You can do whatever else you want here
        // we'll do the obvious
        ct.ctLayout = ctLayout
        return ct.MarshalJSON()
}

call ct.Marshal(your layout)

If you have access to the package in which CustomTime exists go ahead and modify the CustomTime instance directly and make Marshal unexported if you want.

答案2

得分: 1

EntryCustomTimeMarshalJSON方法之间,没有一种自动的方式可以传递信息,即使使用结构标签也不行。我相信,除了创建一个新类型之外,你唯一能做的事情就是在CustomTime中添加一个新字段,然后在序列化Entry实例之前,手动将其设置为某个值,以指示MarshalJSON应该如何格式化输出。我的建议是创建一个新类型,虽然有点重复,但更清晰。

请注意,嵌入允许你避免重新实现那些在嵌入类型和被嵌入类型中应该具有相同行为的方法。所以,在你的情况下,你只需要重新实现JSON序列化和反序列化方法。isset、value和scan方法不需要重新实现。

英文:

There isn't an automatic way to pass information from Entry to CustomTime's MarshalJSON method, not even using the struct tags. The only thing you can do, I believe, apart from creating a new type, is to add a new field to CustomTime and then, just before marshaling the Entry instance, manually set it to some value that will indicate how MarshalJSON should format the output. My suggestion would be to create a new type, a bit repetitive but cleaner.

Note that embedding allows you to avoid having to re-implement those methods that are supposed to behave the same in both, the embedded type and the embedding type. So, in your case, you only need to re-implement the json marshaling and unmarshaling methods. The isset, value, and scan methods don't have to be re-implemented.

huangapple
  • 本文由 发表于 2021年7月20日 11:01:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/68449081.html
匿名

发表评论

匿名网友

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

确定