You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go使用bigquery.NullTimestamp插入BigQuery时报错:字段非记录

问题:BigQuery插入时报错"This field: modified_at is not a record"

API返回的日期字段格式不统一(比如dd-mm-yyyy),我想把这些日期统一转成BigQuery的TIMESTAMP类型。于是把原本的time.Time换成bigquery.NullTimestamp,定义了自定义类型StringDutchDateToTimestamp并实现了UnmarshalJSON方法,但插入数据时一直报错:

错误信息

failed with error: {Location: "modified_at"; Message: "This field: modified_at is not a record."; Reason: "invalid"}

相关代码

type StringDutchDateToTimestamp bigquery.NullTimestamp

type ArticleData struct {
    ModifiedAt         StringDutchDateToTimestamp `json:"modified_at" bigquery:"modified_at"`
    Barcode            string                     `json:"barcode" bigquery:"barcode"`
    PromoStartdate     StringDutchDateToTimestamp `json:"promo_startdate,omitempty" bigquery:"promo_startdate"`
    PromoEnddate       StringDutchDateToTimestamp `json:"promo_enddate,omitempty" bigquery:"promo_enddate"`
}

func (t *StringDutchDateToTimestamp) UnmarshalJSON(b []byte) error {
    if string(b) == "\"\"" || string(b) == "null" {
        t.Valid = false
        t.Timestamp = time.Time{} // Set Timestamp to a zero time value
        return nil
    }

    var dt string
    if err := json.Unmarshal(b, &dt); err != nil {
        return err
    }

    ts, err := time.Parse("02-01-2006", dt)
    if err != nil {
        return err
    }

    t.Timestamp = ts
    t.Valid = true

    return nil
}

// 插入数据的代码片段
inserter := b.Dataset.Table(b.ArticleData).Inserter()
if err := inserter.Put(context.Background(), articles); err != nil {
    return fmt.Errorf("articledata.inserter.put: %w", err)
}

输入数据

{
  "modified_at": "28-06-2023",
  "barcode": "1111111180588",
  "promo_startdate": null,
  "promo_enddate": null
}

spew.Dump()输出

(models.ArticleData) {
 ModifiedAt: (models.StringDutchDateToTimestamp) {
  Timestamp: (time.Time) 2023-06-28 00:00:00 +0000 UTC,
  Valid: (bool) true
 },
 Barcode: (string) (len=13) "1111111180588",
 PromoStartdate: (models.StringDutchDateToTimestamp) {
  Timestamp: (time.Time) 0001-01-01 00:00:00 +0000 UTC,
  Valid: (bool) false
 },
 PromoEnddate: (models.StringDutchDateToTimestamp) {
  Timestamp: (time.Time) 0001-01-01 00:00:00 +0000 UTC,
  Valid: (bool) false
 }
}

BigQuery表结构

modified_at         NULLABLE    TIMESTAMP   
barcode             NULLABLE    STRING      
promo_startdate     NULLABLE    TIMESTAMP   
promo_enddate       NULLABLE    TIMESTAMP

问题原因及解决方法

问题出在自定义类型没有实现BigQuery的类型转换接口。你用类型别名type StringDutchDateToTimestamp bigquery.NullTimestamp定义的自定义类型,不会自动继承原类型的方法,BigQuery客户端无法识别它是TIMESTAMP类型,反而会把它当成包含Timestamp和Valid字段的结构体(也就是错误里说的"record")。

解决步骤:

  • 最简单的方式是用结构体包装bigquery.NullTimestamp,而非类型别名,这样可以复用原类型的所有方法,同时保留自定义的JSON反序列化逻辑:
type StringDutchDateToTimestamp struct {
    bigquery.NullTimestamp
}

func (t *StringDutchDateToTimestamp) UnmarshalJSON(b []byte) error {
    if string(b) == "\"\"" || string(b) == "null" {
        t.Valid = false
        t.Timestamp = time.Time{}
        return nil
    }

    var dt string
    if err := json.Unmarshal(b, &dt); err != nil {
        return err
    }

    ts, err := time.Parse("02-01-2006", dt)
    if err != nil {
        return err
    }

    t.Timestamp = ts
    t.Valid = true

    return nil
}
  • 也可以手动实现driver.Valuer接口,明确告诉BigQuery客户端如何转换这个类型:
func (t StringDutchDateToTimestamp) Value() (driver.Value, error) {
    if !t.Valid {
        return nil, nil
    }
    return t.Timestamp, nil
}

两种方式都能解决问题,推荐第一种结构体包装的方式,更简洁且能复用原类型的所有逻辑。


内容的提问来源于stack exchange,提问作者RemcoE33

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 07:38:14