将JSON日期字符串解析为Golang中的BSON日期。

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

Unmarshal JSON date string to BSON date in Golang

问题

这应该很简单,但我没有取得太多进展。假设我有一个包含UTC日期的JSON,像这样:

{
  "name": "Lex",
  "dob": "2022-11-01T06:30:30.639326208Z"
}

我想将其插入MongoDB集合。在Go中,我这样做:

import (
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

...

var doc any
// 顺便问一下,第二个参数是做什么的?文档中没有提到
_ = bson.UnmarshalExtJSON(jsonBytes, false, &doc)
mongoCollection.InsertOne(context.TODO(), doc)

插入操作是成功的,但dob字段只是一个字符串。如何正确地将其作为日期插入?我的约束条件是:

  1. 我不能更改输入的JSON。
  2. 我不能手动编写Mongo文档,因为我可能处理的是大型不可预测的对象,而且不知道哪些字段/嵌套字段具有日期字符串。

这可行吗?似乎应该有一条明确的途径来实现这个目标。

英文:

This should be easy but I'm not making much headway. Say I have JSON with a UTC date like this:

{
  "name": "Lex",
  "dob": "2022-11-01T06:30:30.639326208Z"
}

I'd like to insert it in a MongoDB collection. In Go, I do:

import (
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

...

var doc any
// by the way what does the 2nd param do?  docs don't mention it
_ = bson.UnmarshalExtJSON(jsonBytes, false, &doc)
mongoCollection.InsertOne(context.TODO(), doc)

The insert works, but the dob field is just a string. What's the correct way to insert it as Date? My constraints are:

  1. I cannot change the input JSON
  2. I can't manually write the Mongo doc because I could be dealing with large unpredictable objects and won't know which fields/nested fields have a date string

Is this doable? Seems like there has to be a paved road for how to do this.

答案1

得分: 2

你可以定义一个自定义的bsoncodec.ValueDecoder解码器来将日期字符串解码为time.Time,然后通过RegisterTypeDecoder进行注册。

func dateTimeDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
    ts, _ := vr.ReadString()
    t, err := time.Parse(time.RFC3339Nano, ts)
    if err != nil {
        return err
    }

    val.Set(reflect.ValueOf(t))

    return nil
}

var tDateTime = reflect.TypeOf(time.Time{})

type TestDoc struct {
    Name string    `bson:"name"`
    Dob  time.Time `bson:"dob"`
}

func createCustomRegistry() *bsoncodec.RegistryBuilder {
    var primitiveCodecs bson.PrimitiveCodecs

    rb := bsoncodec.NewRegistryBuilder()
    bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
    bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
    rb.RegisterTypeDecoder(tDateTime, bsoncodec.ValueDecoderFunc(dateTimeDecodeValue))
    primitiveCodecs.RegisterPrimitiveCodecs(rb)
    return rb
}

或者将日期字符串解析为BSON日期的示例代码

func dateTimeDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
    ts, _ := vr.ReadString()
    t, err := time.Parse(time.RFC3339Nano, ts)
    if err != nil {
        val.SetString(ts)
    } else {
        val.Set(reflect.ValueOf(primitive.DateTime(t.UnixMilli())))
    }

    return nil
}

var tDateTime = reflect.TypeOf(primitive.DateTime(0))

type TestDoc struct {
    Name string             `bson:"name"`
    Dob  primitive.DateTime `bson:"dob"`
}

func createCustomRegistry() *bsoncodec.RegistryBuilder {
    var primitiveCodecs bson.PrimitiveCodecs

    rb := bsoncodec.NewRegistryBuilder()
    bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
    bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
    rb.RegisterTypeDecoder(tDateTime, bsoncodec.ValueDecoderFunc(dateTimeDecodeValue))
    primitiveCodecs.RegisterPrimitiveCodecs(rb)
    return rb
}

然后通过UnmarshalExtJSONWithRegistry使用这个注册器。

func main() {
    jsonBytes := []byte(`{
       "name": "ben",
       "dob": "2022-11-01T06:30:30.639326208Z"
    }`)

    var customRegistry = createCustomRegistry().Build()

    var doc TestDoc
    _ = bson.UnmarshalExtJSONWithRegistry(customRegistry, jsonBytes, false, &doc)
    fmt.Println(doc)
}

完整代码请参考PLAYGROUND

关于Canonical参数,请参考文档

Canonical Extended JSON是基于JSON标准的字符串格式,用于描述BSON文档。Canonical Extended JSON强调类型保留,但牺牲了可读性和互操作性。

英文:

You could define one custom decoders of bsoncodec.ValueDecoder to decode the date string to time.Time, then register it through RegisterTypeDecoder

func dateTimeDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
	ts, _ := vr.ReadString()
	t, err := time.Parse(time.RFC3339Nano, ts)
	if err != nil {
		return err
	} 

	val.Set(reflect.ValueOf(t))

	return nil
}

var tDateTime = reflect.TypeOf(time.Time{})

type TestDoc struct {
	Name string    `bson:"name"`
	Dob  time.Time `bson:"dob"`
}

func createCustomRegistry() *bsoncodec.RegistryBuilder {
	var primitiveCodecs bson.PrimitiveCodecs

	rb := bsoncodec.NewRegistryBuilder()
	bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
	bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
	rb.RegisterTypeDecoder(tDateTime, bsoncodec.ValueDecoderFunc(dateTimeDecodeValue))
	primitiveCodecs.RegisterPrimitiveCodecs(rb)
	return rb
}

Or to parse date string to BSON date sample codes

func dateTimeDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
	ts, _ := vr.ReadString()
	t, err := time.Parse(time.RFC3339Nano, ts)
	if err != nil {
		val.SetString(ts)
	} else {
		val.Set(reflect.ValueOf(primitive.DateTime(t.UnixMilli())))
	}

	return nil
}

var tDateTime = reflect.TypeOf(primitive.DateTime(0))

type TestDoc struct {
	Name string             `bson:"name"`
	Dob  primitive.DateTime `bson:"dob"`
}

func createCustomRegistry() *bsoncodec.RegistryBuilder {
	var primitiveCodecs bson.PrimitiveCodecs

	rb := bsoncodec.NewRegistryBuilder()
	bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
	bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
	rb.RegisterTypeDecoder(tDateTime, bsoncodec.ValueDecoderFunc(dateTimeDecodeValue))
	primitiveCodecs.RegisterPrimitiveCodecs(rb)
	return rb
}

Then use this register through UnmarshalExtJSONWithRegistry


func main() {
	jsonBytes := []byte(`{
       "name": "ben",
       "dob": "2022-11-01T06:30:30.639326208Z"
    }`)

	var customRegistry = createCustomRegistry().Build()

	var doc TestDoc
	_ = bson.UnmarshalExtJSONWithRegistry(customRegistry, jsonBytes, false, &doc)
	fmt.Println(doc)
}

Full codes
<kbd>PLAYGROUND</kbd>


As for Canonical parameter please refer to doc

> Canonical Extended JSON - A string format based on the JSON standard that describes BSON documents. Canonical Extended JSON emphasizes type preservation at the expense of readability and interoperability.

huangapple
  • 本文由 发表于 2022年11月1日 14:44:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/74272391.html
匿名

发表评论

匿名网友

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

确定