|
oleromd A couple of points here:
- We will not be changing the default behavior of time.Time, but it's very simple to create a new BSON registry that will decode BSON timestamps as time.Time instead of int64 when decoding into an interface.
- It's not recommended to go from BSON -> JSON -> a Go struct. BSON and JSON fields do not have a 1-1 mapping and this could cause issues with different types of structs. Furthermore, our BSON library creates a bson.D when decoding into an empty interface and it's currently not possible to uses json.Marshal to convert a bson.D into a JSON object. If your use case requires this, it's better to use the Extended JSON format, which is more inline with BSON. The Go driver has builtin support to do this.
This code snippet shows how to create a new registry and convert a decoded interface{} into a custom struct:
package main
|
|
import (
|
"context"
|
"fmt"
|
"log"
|
"time"
|
"reflect"
|
"github.com/mongodb/mongo-go-driver/bson"
|
"github.com/mongodb/mongo-go-driver/bson/bsontype"
|
"github.com/mongodb/mongo-go-driver/mongo"
|
"github.com/mongodb/mongo-go-driver/mongo/options"
|
)
|
|
type Foo struct {
|
T time.Time
|
}
|
|
func main() {
|
ctx := context.Background()
|
coll, err := initCollection(ctx)
|
if err != nil {
|
log.Fatal(err)
|
}
|
|
c, _ := coll.Find(ctx, bson.D{})
|
for c.Next(ctx) {
|
var obj interface{}
|
if err := c.Decode(&obj); err != nil {
|
log.Fatal(err)
|
}
|
|
foo, err := convertInterfaceToFoo(obj)
|
if err != nil {
|
log.Fatal(err)
|
}
|
|
fmt.Println(foo)
|
}
|
}
|
|
func initCollection(ctx context.Context) (*mongo.Collection, error) {
|
reg := bson.NewRegistryBuilder().RegisterTypeMapEntry(bsontype.Timestamp, reflect.TypeOf(time.Time{})).Build()
|
clientOptions := options.Client().SetRegistry(reg)
|
client, err := mongo.NewClientWithOptions("mongodb://localhost:27017", clientOptions)
|
if err != nil {
|
return nil, err
|
}
|
|
err = client.Connect(ctx)
|
if err != nil {
|
return nil, err
|
}
|
|
db := client.Database("db")
|
coll := db.Collection("coll") foo := Foo{
|
T: time.Now(),
|
}
|
_, err = coll.InsertOne(ctx, foo)
|
if err != nil {
|
return nil, err
|
}
|
|
return coll, nil
|
}
|
|
func convertInterfaceToFoo(obj interface{}) (Foo, error) {
|
extJSONbytes, err := bson.MarshalExtJSON(obj, true, true)
|
if err != nil {
|
return Foo{}, err
|
}
|
|
var foo Foo
|
err = bson.UnmarshalExtJSON(extJSONbytes, true, &foo)
|
if err != nil {
|
return Foo{}, err
|
}
|
|
return foo, nil
|
}
|
|
|
Whoops, I've made a little mistake in code (type castng should be replaced with marhalling/unmarshalling), sorry. But the main idea is the same. Here's small example:
awesomeProject3/example/mgo.go:
package example
|
|
import (
|
"time"
|
"fmt"
|
"encoding/json"
|
|
"gopkg.in/mgo.v2"
|
"gopkg.in/mgo.v2/bson"
|
)
|
|
type StructOne struct {
|
One time.Time `bson:"one"`
|
Two string `bson:"two"`
|
}
|
|
func MGODriver() {
|
session, err := mgo.DialWithTimeout("mongodb://localhost", time.Second)
|
if err != nil {
|
panic(err)
|
}
|
|
example := StructOne{
|
One: time.Now(),
|
Two: "Some value",
|
}
|
collection := session.DB("mgo").C("one")
|
|
if err := collection.Insert(example); err != nil {
|
panic(err)
|
}
|
|
query := bson.M{"two": example.Two}
|
|
var selectValue interface{}
|
if err := collection.Find(query).One(&selectValue); err != nil {
|
panic(err)
|
}
|
|
fmt.Println(selectValue)
|
|
var structOne StructOne
|
if marshaledValue, err := json.Marshal(selectValue); err != nil {
|
panic(err)
|
} else {
|
if err := json.Unmarshal(marshaledValue, &structOne); err != nil {
|
panic(err)
|
}
|
}
|
|
fmt.Println(structOne)
|
}
|
|
awesomeProject3/example/mongo.go:
package example
|
|
import (
|
"context"
|
"time"
|
"fmt"
|
|
"github.com/mongodb/mongo-go-driver/mongo"
|
"github.com/mongodb/mongo-go-driver/bson"
|
"encoding/json"
|
)
|
|
func MongoGoDriver() {
|
client, err := mongo.Connect(context.TODO(), "mongodb://localhost")
|
if err != nil {
|
panic(err)
|
}
|
|
database := client.Database("example")
|
collection := database.Collection("one")
|
|
object := StructOne{
|
One: time.Now(),
|
Two: "two",
|
}
|
collection.InsertOne(context.TODO(), object)
|
|
filter := bson.M{"one": object.One}
|
cursor, err := collection.Find(context.TODO(), filter)
|
if err != nil {
|
panic(err)
|
}
|
|
for cursor.Next(context.TODO()) {
|
var object bson.M
|
if err := cursor.Decode(&object); err != nil {
|
panic(err)
|
}
|
|
fmt.Println(object)
|
|
var structOne StructOne
|
if marshaledValue, err := json.Marshal(object); err != nil {
|
panic(err)
|
} else {
|
if err := json.Unmarshal(marshaledValue, &structOne); err != nil {
|
// panic: parsing time "1548944996876" as ""2006-01-02T15:04:05Z07:00"": cannot parse "1548944996876" as """
|
// Since type of object.One is int64, not time.Time
|
// But everything goes ok if decoding is happening with StructOne type. Ex:
|
// var object StructOne
|
// if err := cursor.Decode(&object); err != nil {
|
// panic(err)
|
// }
|
// Field One is decoded correctly
|
panic(err)
|
}
|
}
|
|
fmt.Println(structOne)
|
}
|
}
|
awesomeProject3/main.go
package main
|
|
import "awesomeProject3/example"
|
|
func main() {
|
//example.MGODriver()
|
example.MongoGoDriver()
|
}
|
|
So I wonder if DateTime as int64 can cause some other unexpected problems while migration
|
|
oleromd I'm not sure this is possible. The default decoder for an empty interface will generate a bson.D and as far as I know, it's not possible to typecast a bson.D to a struct type. Can you link the code that accomplished this with mgo?
|
|
Thank you for reply, @divjot.arora
My point is that it is impossible to unmarshall cursor's result into empty interface and cast it later (mgo driver allows this feature). And it also brakes generic alike code. Since now I have to implement low-level methods for each object type (it's a little bit dirty).
Ex, I have 3 structures: StructOne, StructTwo, StructThree. And I don't want to re-implement 3 methods for each structure. Instead I want to use one method (with usage of empty interface) since there are no generics.
Will DateTime still be int64?
|
|
oleromd DateTimes are serialized to the server as int64. When you unmarshal the cursor's result into an interface, the datetime will be unmarshalled as int64 as well. Instead, you can umarshal directly to the SomeObject type.
for cursor.Next(context.TODO()) {
|
var someObj SomeObject
|
if err := cursor.Decode(&someObj); err != nil {
|
panic(err)
|
}
|
|
fmt.Println(someObj)
|
}
|
|
|
Example:
package main
|
|
import (
|
"context"
|
"time"
|
"fmt"
|
|
"github.com/mongodb/mongo-go-driver/mongo"
|
"github.com/mongodb/mongo-go-driver/bson"
|
)
|
|
type SomeObject struct {
|
A time.Time `bson:"a"`
|
}
|
|
func main() {
|
client, err := mongo.Connect(context.TODO(), "mongodb://localhost")
|
if err != nil {
|
panic(err)
|
}
|
|
database := client.Database("example")
|
collection := database.Collection("one")
|
|
object := SomeObject{A: time.Now()}
|
collection.InsertOne(context.TODO(), object)
|
|
filter := bson.M{"a": object.A}
|
cursor, err := collection.Find(context.TODO(), filter)
|
if err != nil {
|
panic(err)
|
}
|
|
for cursor.Next(context.TODO()) {
|
var object interface{}
|
if err := cursor.Decode(&object); err != nil {
|
panic(err)
|
}
|
fmt.Println(object)
|
|
if _, ok := object.(SomeObject); !ok {
|
fmt.Println("Unexpected type of a field. Expected time.Time, got int64")
|
}
|
}
|
}
|
|
|
Generated at Thu Feb 08 08:34:56 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.