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

使用xitongsys/parquet-go转结构体到Parquet时出现空指针解引用错误

问题:自定义Timestamp结构体导致Parquet写入时空指针panic

运行时触发错误:

runtime error: invalid memory address or nil pointer dereference

相关代码及配置如下:

结构体与方法定义

type MyTs struct {
    time.Time
}

func (myts *MyTs) UnmarshalJSON(d []byte) error {
    str := string(d)
    str = str[1 : len(str)-1] // 去除首尾双引号

    // 定义时间解析格式
    layout1 := "2006-01-02T15:04:05+0000"
    layout2 := "2006-01-02T15:04:05.999999Z"

    // 尝试第一种格式解析
    t, err := time.Parse(layout1, str)
    if err != nil {
        // 失败则尝试第二种格式
        t, err = time.Parse(layout2, str)
        if err != nil {
            return err
        }
    }

    myts.Time = t
    return nil
}

func (myts *MyTs) String() string {
    stringTime := fmt.Sprintf("%d", myts.Time.UnixNano()/1000000)
    res := fmt.Sprintf("{Time:%s}", fmt.Sprint(stringTime))
    return res
}

type StructData struct {
    ID        string `bson:"id" json:"id" parquet:"name=id, type=BYTE_ARRAY, convertedtype=UTF8, encoding=PLAIN_DICTIONARY"`
    Timestamp MyTs   `parquet:"name=timestamp, type=INT64, logicaltype=TIMESTAMP_MILLIS"`
}

go.mod配置

require (
    github.com/xitongsys/parquet-go v1.6.2
    github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0
)

注释掉StructData中的Timestamp字段后,Parquet写入函数可正常运行。不强制要求输出为TIMESTAMP_MILLIS类型,接受其他兼容方案。


问题原因

空指针panic的核心是:parquet-go库在处理自定义MyTs类型时,因该类型的核心方法(如UnmarshalJSON、String)使用指针接收者,但StructData中Timestamp为值类型字段。当字段未显式初始化时,库通过反射操作时会触发对nil指针的方法调用,导致panic。


解决方案

方案1:将Timestamp改为指针类型

修改StructData的字段定义,把Timestamp从值类型改为指针类型,确保初始化时分配内存:

type StructData struct {
    ID        string  `bson:"id" json:"id" parquet:"name=id, type=BYTE_ARRAY, convertedtype=UTF8, encoding=PLAIN_DICTIONARY"`
    Timestamp *MyTs   `parquet:"name=timestamp, type=INT64, logicaltype=TIMESTAMP_MILLIS"`
}

使用时需显式初始化:

data := StructData{
    ID: "sample-id",
    Timestamp: &MyTs{Time: time.Now()},
}

方案2:将MyTs的方法改为值接收者

把UnmarshalJSON和String方法的接收者改为值类型,避免指针依赖:

func (myts MyTs) UnmarshalJSON(d []byte) (MyTs, error) {
    str := string(d)
    str = str[1 : len(str)-1]

    layout1 := "2006-01-02T15:04:05+0000"
    layout2 := "2006-01-02T15:04:05.999999Z"

    t, err := time.Parse(layout1, str)
    if err != nil {
        t, err = time.Parse(layout2, str)
        if err != nil {
            return MyTs{}, err
        }
    }

    myts.Time = t
    return myts, nil
}

func (myts MyTs) String() string {
    stringTime := fmt.Sprintf("%d", myts.Time.UnixNano()/1000000)
    res := fmt.Sprintf("{Time:%s}", stringTime)
    return res
}

方案3:直接使用int64存储时间戳

放弃自定义类型,用int64存储毫秒级时间戳,适配Parquet的INT64类型:

type StructData struct {
    ID        string `bson:"id" json:"id" parquet:"name=id, type=BYTE_ARRAY, convertedtype=UTF8, encoding=PLAIN_DICTIONARY"`
    Timestamp int64  `parquet:"name=timestamp, type=INT64"`
}

使用时转换时间格式:

data := StructData{
    ID: "sample-id",
    Timestamp: time.Now().UnixNano() / 1000000,
}

若需保留JSON解析逻辑,可在StructData上单独实现UnmarshalJSON方法,手动解析时间字符串并转为时间戳。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:05:18