使用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
相关产品推荐
相关产品推荐

