Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-125

Enum deserialization appears to rely on the underlying type of the CURRENT C# Enum to deserialize Enums - backward compatibility issue?

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 0.9
    • Affects Version/s: 0.7
    • Component/s: None
    • Labels:
      None

      Reading through the code for EnumSerializer.Deserialize there is a switch statement that uses the underlying type of the C# Enum being deserialized to determine what to read from the database. Isn't this backwards? Shouldn't it read the value from the database according to the stored Bson type (whatever it is: Int32, Int64, double, ...) and THEN see if it can fit that value into the CURRENT underlying type of the Enum?

      Suppose I have a flags Enum today that fits in an Int32. That will get saved to the database as a BSon int32. Now I add one more flag that pushes it to need an Int64. Next time I save a value it gets saved as a Bson Int64, that's fine and I can read it back out. BUT now, none of my existing documents (that were serialized when my Enum used to fit in an Int32) will deserialize because this code will look at the current underlying type (Int64) and then try to read an Int64 when in fact the legacy records have Int32's in them.

      The alternative direction is also possible but less likely (perhaps you have a flags enum with a high order bit that has never been used - you remove it from the enum and now it fits in a smaller underlying type).

      Suggestion:

      Read whatever numeric type is in the database, then check that it fits in the CURRENT underlying type.

      Separately, allowing this code to also read a BSon Double and returning an Enum from that will solve a different issue which is that Enums round-tripped through Map Reduce come back as doubles and then can't be deserialized back into an Enum.

      Excuse the ugly repetition ... but for the purposes of illustrating what I mean ... something like this seems to work and will solve both the backward compatibility issue and the map-reduce round trip problem.

      public override object Deserialize(
      BsonReader bsonReader,
      Type nominalType
      ) {
      VerifyNominalType(nominalType);
      var bsonType = bsonReader.CurrentBsonType;
      if (bsonType == BsonType.String)

      { var value = bsonReader.ReadString(); return Enum.Parse(nominalType, value); }

      else if (bsonType == BsonType.Double)
      {
      int value = (int)bsonReader.ReadDouble();
      switch (Type.GetTypeCode(Enum.GetUnderlyingType(nominalType)))

      { case TypeCode.Byte: return (byte) value; case TypeCode.Int16: return (short) value; case TypeCode.Int32: return (int)value; case TypeCode.Int64: return (long)value; case TypeCode.SByte: return (sbyte) value; case TypeCode.UInt16: return (ushort) value; case TypeCode.UInt32: return (uint) value; case TypeCode.UInt64: return (ulong) value; default: throw new BsonSerializationException("Unrecognized underlying type for enum"); }

      }
      else if (bsonType == BsonType.Int32)
      {
      int value = bsonReader.ReadInt32();
      switch (Type.GetTypeCode(Enum.GetUnderlyingType(nominalType)))

      { case TypeCode.Byte: return (byte)value; case TypeCode.Int16: return (short)value; case TypeCode.Int32: return (int)value; case TypeCode.Int64: return (long)value; case TypeCode.SByte: return (sbyte)value; case TypeCode.UInt16: return (ushort)value; case TypeCode.UInt32: return (uint)value; case TypeCode.UInt64: return (ulong)value; default: throw new BsonSerializationException("Unrecognized underlying type for enum"); }

      }
      else if (bsonType == BsonType.Int64)
      {
      long value = bsonReader.ReadInt64();
      switch (Type.GetTypeCode(Enum.GetUnderlyingType(nominalType)))

      { case TypeCode.Byte: return (byte)value; case TypeCode.Int16: return (short)value; case TypeCode.Int32: return (int)value; case TypeCode.Int64: return (long)value; case TypeCode.SByte: return (sbyte)value; case TypeCode.UInt16: return (ushort)value; case TypeCode.UInt32: return (uint)value; case TypeCode.UInt64: return (ulong)value; default: throw new BsonSerializationException("Unrecognized underlying type for enum"); }

      }
      else
      throw new BsonSerializationException("Enums can't be read from " + bsonType);
      }

            Assignee:
            robert@mongodb.com Robert Stam
            Reporter:
            ianmercer Ian Mercer
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved: