[CSHARP-125] Enum deserialization appears to rely on the underlying type of the CURRENT C# Enum to deserialize Enums - backward compatibility issue? Created: 04/Dec/10  Updated: 02/Apr/15  Resolved: 04/Dec/10

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: 0.7
Fix Version/s: 0.9

Type: Bug Priority: Major - P3
Reporter: Ian Mercer Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

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);
}



 Comments   
Comment by Robert Stam [ 04/Dec/10 ]

Changed EnumSerializer to add Int64 representation and to be flexible about converting between numeric types when deserializing. Added more unit tests for enums of different underlying integeral types.

Generated at Wed Feb 07 21:35:53 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.