简体   繁体   中英

MongoDb C# driver - serializing List<enum> as string[]

I have a class with a property of type List<SomeEnum> . Something like this:

public enum MyEnum
{
  A,
  B
}

public class MyClass
{
    public string Id { get; set; }
    public List<MyEnum> Values { get; set; }
}

I'm already using the EnumRepresentationConvention in this way:

ConventionRegistry.Register("EnumStringConvention", new ConventionPack { new EnumRepresentationConvention(BsonType.String) }, t => true);

Still, the Values property gets serialized as an array of ints (simple enum properties are correctly handled as ints). It seem the convention is not used in the context of the list serialization.

How can I enforce the serializer to write strings instead of ints?

Instead of calling ConventionRegistry.Register() , add data annotation [BsonRepresentation(BsonType.String)] to the property Values of MyClass.

public class MyClass
{
    public string Id { get; set; }

    [BsonRepresentation(BsonType.String)] 
    public List<MyEnum> Values { get; set; }
}

After that change collection.InsertOne(obj); is saving this:

{
    "_id" : "1",
    "Values" : [ 
        "A", 
        "B"
    ]
}

Alas this PR has been closed as won't fix. https://github.com/mongodb/mongo-csharp-driver/pull/305#issuecomment-731475503 .

Good reasons are given for this, but it is the kind of thing that I think people want to do, so depending on your appetite for jumping through hoops you can try this (.NET 5):

using System;
using System.Collections.Generic;
using System.Reflection;

using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;

public sealed class EnumWrapper<TEnum>
    where TEnum : struct, Enum
{
    [BsonConstructor]
    public EnumWrapper(TEnum value) => this.Value = value;

    public TEnum Value { get; }

    public static readonly IBsonSerializer<EnumWrapper<TEnum>> Serializer = new BsonSerializerImpl();

    public static implicit operator TEnum(EnumWrapper<TEnum> wrapper) => wrapper.Value;

    public static implicit operator EnumWrapper<TEnum>(TEnum value) => new(value);

    public override bool Equals(object obj) =>
        obj is EnumWrapper<TEnum> wrapper
        && EqualityComparer<TEnum>.Default.Equals(this.Value, wrapper.Value);

    public override int GetHashCode() => HashCode.Combine(this.Value);

    public override string ToString() => this.Value.ToString();

    private class BsonSerializerImpl : IBsonSerializer<EnumWrapper<TEnum>>
    {
        public Type ValueType => typeof(EnumWrapper<TEnum>);

        public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EnumWrapper<TEnum> value) =>
            context.Writer.WriteString(((TEnum)value).ToString());

        public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) =>
            this.Serialize(context, args, (EnumWrapper<TEnum>)value);

        object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
            this.Deserialize(context, args);

        public EnumWrapper<TEnum> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
            Enum.Parse<TEnum>(context.Reader.ReadString());
    }
}

public class EnumWrapperBsonSerializationProvider : IBsonSerializationProvider
{
    public IBsonSerializer GetSerializer(Type type)
    {
        if (!type.IsGenericType)
        {
            return null;
        }

        var typeDefinition = type.GetGenericTypeDefinition();
        if (typeDefinition != typeof(EnumWrapper<>))
        {
            return null;
        }

        var field = type.GetField(nameof(EnumWrapper<Hack>.Serializer), BindingFlags.Public | BindingFlags.Static);
        return (IBsonSerializer)field.GetValue(null);
    }

    private enum Hack { }
}

Now you can use EnumWrapper<TEnum> mostly wherever you would normally use TEnum and it will do what you want. If you don't register the serialization provider it will serialize as nested objects, so before doing anything you should call this:

BsonSerializer.RegisterSerializationProvider(new EnumWrapperBsonSerializationProvider());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM