如何在解组 MongoDB 文档时忽略空值? [英] How to ignore nulls while unmarshalling a MongoDB document?

查看:39
本文介绍了如何在解组 MongoDB 文档时忽略空值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

type User struct {名称字符串`bson:"name"`电子邮件字符串`bson:"email"`}

改变在这个结构中声明的类型不是一个选项,问题就在这里;在我无法完全控制的 MongoDB 数据库中,某些文档已插入空值,而最初我并不期待空值.像这样:

<代码>{"name": "约翰·多伊",电子邮件":空}

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

防止将此类文档插入数据库将是理想的解决方案,但对于我的用例,忽略空值也是可以接受的.因此,在解组文档后,我的 User 实例将如下所示

用户{名称:约翰·多伊",电子邮件: "",}

我试图找到一些注释标志,或者可以传递给方法 Find/FindOne 的选项,或者甚至是一个查询参数防止从数据库返回任何包含空值的字段.到目前为止没有任何成功.

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

解决方案

问题是目前的bson编解码器不支持将string编码/解码为/from null.

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

自定义解码器由 bsoncodec 类型描述.值解码器.它们可以在 bsoncodec.Registry,使用 bsoncodec.RegistryBuilder 例如.

可以在多个级别设置/应用注册表,甚至可以应用于整个 mongo.Client,或到 mongo.Database 或者只是一个 mongo.Collection,在获取它们时,作为其选项的一部分,例如options.ClientOptions.SetRegistry().

首先让我们看看如何为 string 做到这一点,接下来我们将看看如何改进/将解决方案推广到任何类型.

1.处理 null 字符串

首先,让我们创建一个自定义字符串解码器,它可以将 null 转换为 (n 空) 字符串:

import (go.mongodb.org/mongo-driver/bson/bsoncodec"go.mongodb.org/mongo-driver/bson/bsonrw"go.mongodb.org/mongo-driver/bson/bsontype")类型 nullawareStrDecoder 结构{}func (nullawareStrDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) 错误{如果 !val.CanSet() ||val.Kind() !=reflect.String {return errors.New("类型错误或不可设置")}var str 字符串变量错误切换 vr.Type() {案例 bsontype.String:如果 str, err = vr.ReadString();错误!= 零{返回错误}case bsontype.Null://这是处理 NULL 的缺失部分!如果 err = vr.ReadNull();错误!= 零{返回错误}默认:return fmt.Errorf("无法将 %v 解码为字符串类型", vr.Type())}val.SetString(str)返回零}

好的,现在让我们看看如何将这个自定义字符串解码器用于mongo.Client:

clientOpts := options.Client().ApplyURI("mongodb://localhost:27017/").设置注册表(bson.NewRegistryBuilder().RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).建造(),)客户端,错误:= mongo.Connect(ctx,clientOpts)

从现在开始,使用这个client,每当你将结果解码为string值时,这个注册的nullawareStrDecoder解码器将被调用来处理转换,它接受 bson null 值并设置 Go 空字符串 "".

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

2.处理任何类型的 null 值:类型中立"空感知解码器

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

我们可以(也应该)做的是创建一个单独的、类型中立"的自定义解码器,它只处理 null ,并且如果 BSON 值不是 null,应该调用默认解码器来处理非null 值.

这出奇的简单:

type nullawareDecoder struct {defDecoder bsoncodec.ValueDecoderzeroValue 反映.Value}func (d *nullawareDecoder) DecodeValue(dctx bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) 错误{如果 vr.Type() != bsontype.Null {返回 d.defDecoder.DecodeValue(dctx, vr, val)}如果 !val.CanSet() {return errors.New("值不可设置")}如果错误:= vr.ReadNull();错误!= 零{返回错误}//设置 val 类型的零值:val.Set(d.zeroValue)返回零}

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

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

customValues := []interface{}{,      //细绳int(0),//整数int32(0),//int32}rb := bson.NewRegistryBuilder()对于 _, v := 范围 customValues {t :=reflect.TypeOf(v)defDecoder,错误:= bson.DefaultRegistry.LookupDecoder(t)如果错误!= nil {恐慌(错误)}rb.RegisterDecoder(t, &nullawareDecoder{defDecoder,reflect.Zero(t)})}clientOpts := options.Client().ApplyURI("mongodb://localhost:27017/").SetRegistry(rb.Build())客户端,错误:= mongo.Connect(ctx,clientOpts)

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

I would like to know if there's any approach that would allow me to ignore null types while unmarshalling a MongoDB document into a Go struct.

Right now I have some auto-generate Go structs, something like this:

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

Changing the types declared in this struct is not an option, and here's the problem; in a MongoDB database, which I do not have total control, some of the documents have been inserted with null values were originally I was not expecting nulls. Something like this:

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

As the string types declared inside my struct are not pointers, they can't receive a nil value, so whenever I try to unmarshall this document in my struct, it returns an error.

Preventing the insertion of this kind of document into the database would be the ideal solution, but for my use case, ignoring the null values would also be acceptable. So after unmarshalling the document my User instance would look like this

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

I'm trying to find, either some annotation flag, or an option that could be passed to the method Find/FindOne, or maybe even a query parameter to prevent returning any field containing null values from the database. Without any success until now.

Are there any built-in solutions in the mongo-go-driver for this problem?

解决方案

The problem is that the current bson codecs do not support encoding / decoding string into / from null.

One way to handle this is to create a custom decoder for string type in which we handle null values: we just use the empty string (and more importantly don't report error).

Custom decoders are described by the type bsoncodec.ValueDecoder. They can be registered at a bsoncodec.Registry, using a bsoncodec.RegistryBuilder for example.

Registries can be set / applied at multiple levels, even to a whole mongo.Client, or to a mongo.Database or just to a mongo.Collection, when acquiring them, as part of their options, e.g. options.ClientOptions.SetRegistry().

First let's see how we can do this for string, and next we'll see how to improve / generalize the solution to any type.

1. Handling null strings

First things first, let's create a custom string decoder that can turn a null into a(n empty) string:

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
}

OK, and now let's see how to utilize this custom string decoder to a mongo.Client:

clientOpts := options.Client().
    ApplyURI("mongodb://localhost:27017/").
    SetRegistry(
        bson.NewRegistryBuilder().
            RegisterDecoder(reflect.TypeOf(""), nullawareStrDecoder{}).
            Build(),
    )
client, err := mongo.Connect(ctx, clientOpts)

From now on, using this client, whenever you decode results into string values, this registered nullawareStrDecoder decoder will be called to handle the conversion, which accepts bson null values and sets the Go empty string "".

But we can do better... Read on...

2. Handling null values of any type: "type-neutral" null-aware decoder

One way would be to create a separate, custom decoder and register it for each type we wish to handle. That seems to be a lot of work.

What we may (and should) do instead is create a single, "type-neutral" custom decoder which handles just nulls, and if the BSON value is not null, should call the default decoder to handle the non-null value.

This is surprisingly simple:

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
}

We just have to figure out what to use for nullawareDecoder.defDecoder. For this we may use the default registry: bson.DefaultRegistry, we may lookup the default decoder for individual types. Cool.

So what we do now is register a value of our nullawareDecoder for all types we want to handle nulls for. It's not that hard. We just list the types (or values of those types) we want this for, and we can take care of all with a simple loop:

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)

In the example above I registered null-aware decoders for string, int and int32, but you may extend this list to your liking, just add values of the desired types to the customValues slice above.

这篇关于如何在解组 MongoDB 文档时忽略空值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆