在解组MongoDB文档时如何忽略null?

时间:2019-11-21 22:02:34

标签: mongodb go mongodb-query bson mongo-go

我想知道是否有任何方法可以让我在将MongoDB文档解组为Go结构时忽略空类型。

现在我有一些自动生成的Go结构,如下所示:

type User struct {
  Name  string `bson:"name"`
  Email string `bson:"email"`
}

更改此结构中声明的类型不是一种选择,这就是问题所在;在我没有完全控制权的MongoDB数据库中,某些文档原本是用空值插入的,但我本来并不希望使用空值。像这样:

{
  "name": "John Doe",
  "email": null
}

由于在我的结构体中声明的字符串类型不是指针,它们不能接收nil值,因此,每当我尝试在我的结构体中解组该文档时,它都会返回错误。

防止这种类型的文档插入数据库将是理想的解决方案,但是对于我的用例,忽略空值也是可以接受的。因此,在将文档解组后,我的User实例看起来像这样

User {
  Name:  "John Doe",
  Email: "",
}

我正在尝试查找一些注释标记或可以传递给方法Find / FindOne的选项,甚至可能是一个查询参数,以防止返回任何包含null的字段数据库中的值。到目前为止没有任何成功。

mongo-go-driver中是否有针对此问题的内置解决方案?

3 个答案:

答案 0 :(得分:4)

问题是当前的bson编解码器不支持将string编码/解码为null或从string解码。

一种解决方法是为null类型创建自定义解码器,在其中处理string值:我们只使用空字符串(更重要的是不报告错误)。 / p>

自定义解码器的类型为bsoncodec.ValueDecoder。可以在bsoncodec.Registry上注册它们,例如使用bsoncodec.RegistryBuilder

可以在多个级别上设置/应用注册表,甚至可以将其应用于整个mongo.Clientmongo.Databasemongo.Collection,作为其选项的一部分,例如options.ClientOptions.SetRegistry()

首先让我们看看如何为null做到这一点,然后我们将看看如何将解决方案改进/推广为任何类型。

1。处理null字符串

首先,让我们创建一个自定义字符串解码器,该解码器可以将import ( "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/bsonrw" "go.mongodb.org/mongo-driver/bson/bsontype" ) type nullawareStrDecoder struct{} func (nullawareStrDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error { if !val.CanSet() || val.Kind() != reflect.String { return errors.New("bad type or not settable") } var str string var err error switch vr.Type() { case bsontype.String: if str, err = vr.ReadString(); err != nil { return err } case bsontype.Null: // THIS IS THE MISSING PIECE TO HANDLE NULL! if err = vr.ReadNull(); err != nil { return err } default: return fmt.Errorf("cannot decode %v into a string type", vr.Type()) } val.SetString(str) return nil } 转换为(n个空)字符串:

mongo.Client

好的,现在让我们看看如何将此自定义字符串解码器用于clientOpts := options.Client(). ApplyURI("mongodb://localhost:27017/"). SetRegistry( bson.NewRegistryBuilder(). RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}). Build(), ) client, err := mongo.Connect(ctx, clientOpts)

client

从现在开始,使用此string,每当您将结果解码为nullawareStrDecoder个值时,都会调用此注册的null解码器来处理转换,该转换接受bson {{1} }并设置Go空字符串""

但是我们可以做得更好...继续阅读...

2。处理任何类型的null值:“类型无关”的可感知空值的解码器

一种方法是创建一个单独的自定义解码器,并为我们希望处理的每种类型注册它。这似乎是很多工作。

我们可能(并且应该)做的是创建一个仅处理null的“类型中立”的自定义解码器,并且如果BSON值不是null,则应调用默认的解码器来处理非null值。

这非常简单:

type nullawareDecoder struct {
    defDecoder bsoncodec.ValueDecoder
    zeroValue  reflect.Value
}

func (d *nullawareDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
    if vr.Type() != bsontype.Null {
        return d.defDecoder.DecodeValue(dctx, vr, val)
    }

    if !val.CanSet() {
        return errors.New("value not settable")
    }
    if err := vr.ReadNull(); err != nil {
        return err
    }
    // Set the zero value of val's type:
    val.Set(d.zeroValue)
    return nil
}

我们只需要弄清楚nullawareDecoder.defDecoder使用什么。为此,我们可以使用默认注册表:bson.DefaultRegistry,我们可以为各个类型查找默认解码器。很好。

所以我们现在要做的是为我们要处理的nullawareDecoder的所有类型注册null的值。没那么难。我们只列出我们想要的类型(或这些类型的值),我们可以通过一个简单的循环来处理所有事情:

customValues := []interface{}{
    "",       // string
    int(0),   // int
    int32(0), // int32
}

rb := bson.NewRegistryBuilder()
for _, v := range customValues {
    t := reflect.TypeOf(v)
    defDecoder, err := bson.DefaultRegistry.LookupDecoder(t)
    if err != nil {
        panic(err)
    }
    rb.RegisterDecoder(t, &nullawareDecoder{defDecoder, reflect.Zero(t)})
}

clientOpts := options.Client().
    ApplyURI("mongodb://localhost:27017/").
    SetRegistry(rb.Build())
client, err := mongo.Connect(ctx, clientOpts)

在上面的示例中,我为stringintint32注册了空感知解码器,但是您可以根据自己的喜好扩展此列表,只需将所需类型的值添加到上面的customValues片。

答案 1 :(得分:0)

您可以通过运算符$existsQuery for Null or Missing Fields进行详细说明。

在mongo-go-driver中,您可以尝试以下查询:

电子邮件=>无查询匹配文档,其中包含电子邮件字段,其值为 不包含电子邮件字段

cursor, err := coll.Find(
   context.Background(),
   bson.D{
      {"email", nil},
})

您只需要在上述查询中添加$ne运算符即可获取没有字段电子邮件或电子邮件中没有值nil的记录。有关运算符$ne

的更多详细信息

答案 2 :(得分:0)

如果您提前知道mongoDB记录中哪些字段可能为空,则可以在结构中使用指针:

type User struct {
  Name  string `bson:"name"` // Will still fail to decode if null in Mongo
  Email *string `bson:"email"` // Will be nil in go if null in Mongo
}

请记住,现在您需要对从mongo解码后使用此值的任何东西进行更防御性的编码,例如:

var reliableVal string
if User.Email != nil {
    reliableVal = *user.Email
} else {
    reliableVal = ""
}