【问题标题】:Generic approach for marshalling a struct's time.Time fields to JSON with custom formatting使用自定义格式将结构的 time.Time 字段编组为 JSON 的通用方法
【发布时间】:2018-09-21 22:58:18
【问题描述】:

我的数据模型定义了多个结构,它们都有两个共同的字段:StartDateEndDate。我需要在编组的 JSON 中将这两个字段格式化为 2018-09-21,因此这些结构实现了 Marshaller 接口:

type Results struct {
    Source     string    `json:"source"`
    StartDate  time.Time 
    EndDate    time.Time 
}

type WeightedResults struct {
    Source          string           `json:"source"`
    StartDate       time.Time        
    EndDate         time.Time        
}

func (r Results) MarshalJSON() ([]byte, error) {
    type Alias Results
    if equalDate(r.StartDate, r.EndDate) {
        return json.Marshal(&struct {
            Date string `json:"date"`
            Alias
        }{
            Date:  r.StartDate.Format(dateFormat),
            Alias: (Alias)(r),
        })
    }    
    return json.Marshal(&struct {
        StartDate string `json:"start_date"`
        EndDate   string `json:"end_date"`
        Alias
    }{
        StartDate: r.StartDate.Format("2006-01-02"),
        EndDate:   r.EndDate.Format("2006-01-02"),
        Alias:     (Alias)(r),
    })
}

func (r WeightedResults) MarshalJSON() ([]byte, error) {
    type Alias WeightedResults
    if equalDate(r.StartDate, r.EndDate) {
        return json.Marshal(&struct {
            Date string `json:"date"`
            Alias
        }{
            Date:  r.StartDate.Format(dateFormat),
            Alias: (Alias)(r),
        })
    } 
    return json.Marshal(&struct {
        StartDate string `json:"start_date"`
        EndDate   string `json:"end_date"`
        Alias
    }{
        StartDate: r.StartDate.Format("2006-01-02"),
        EndDate:   r.EndDate.Format("2006-01-02"),
        Alias:     (Alias)(r),
    })
}

上述解决方案运行良好,但会产生大量代码重复。有没有办法重构MarshalJSON 的两个实现以使用相同的逻辑/代码?我很清楚 Go 不提供泛型(还),但必须有另一种方法来解决这个问题,对吧?

【问题讨论】:

  • 如果您能够更改模型/json 的结构,那么您可以使用“日期范围”类型来解决您的问题。 (例如play.golang.org/p/Hmvr0sW9gnj

标签: json generics go marshalling


【解决方案1】:

您的自定义封送拆收器不应位于结构上,而应位于嵌入 time.Time 的自定义类型上:

type MyTime struct {
    time.Time
}

func (t MyTime) MarshalJSON() ([]byte, error) {
    return json.Marshal(t.Format("2006-01-02"))
}

然后在任何你想要的地方使用这种类型。

type Results struct {
    Source     string    `json:"source"`
    StartDate  MyTime
    EndDate    MyTime
}

【讨论】:

  • 当然可以。但是,我的自定义编组器实现有点复杂,并且依赖于StartDateEndDate。更具体地说,如果 EndDateStartDate 具有相同的日期,我想省略它(更新了代码以便更好地理解)。
  • MarshalJSON() 必须返回 JSON 文本,你的只返回一个“go”字符串。你必须用引号“包裹”它,所以返回:return []byte(`"` + t.Format("2006-01-02") + `"`), nil。更好的是,让json 包正确地转义它:return json.Marshal(t.Format("2006-01-02"))。另外,我将定义带有非指针接收器的自定义封送拆收器,否则它仅在字段是指针或使用指向嵌入器结构的指针(方法集)时才有效。另一种解决方案可能是不嵌入time.Time,而是创建一个新类型,如type MyTime time.Time
  • 哦,确实比较复杂。
  • @icza:回复type MyTime time.Time,我倾向于避免这些实现,因为人们失去了对原始类型定义方法的使用。但在某些情况下,它仍然是合适的。
  • @Flimzy 关于丢失方法是真的。另请注意omitempty 不适用于time.Time,详情请参阅:Golang JSON omitempty With time.Time Field
猜你喜欢
  • 1970-01-01
  • 2017-09-07
  • 2023-03-09
  • 2021-08-12
  • 2019-03-28
  • 1970-01-01
  • 2011-12-31
  • 2022-01-12
  • 1970-01-01
相关资源
最近更新 更多