[CSHARP-4398] InvalidCastException for serialization of RawBsonDocument on the field BsonNull Created: 04/Nov/22  Updated: 07/Nov/22

Status: Backlog
Project: C# Driver
Component/s: Serialization
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Unknown
Reporter: Simona Kamberi Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Summary

I try to serialize/deserialize BsonDocument. We are using explicit field level encryption and MongoDbDriver 2.17.1

We decrypt the value and parse it to BsonDocument. When we want to return serialized model from API that contains a field of type BsonDocument, we receive this error:

 

System.InvalidCastException: Specified cast is not valid.
   at MongoDB.Bson.BsonValue.System.IConvertible.ToType(Type conversionType, IFormatProvider provider)
   at Newtonsoft.Json.JsonWriter.ResolveConvertibleValue(IConvertible convertible, PrimitiveTypeCode& typeCode, Object& value)
   at Newtonsoft.Json.JsonWriter.WriteValue(JsonWriter writer, PrimitiveTypeCode typeCode, Object value)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) 

It happens for the field BsonNull which is regular type of Bson, but it doesnt exist in this method:

 

 

object IConvertible.ToType(Type conversionType, IFormatProvider provider)
{
    if (conversionType == typeof(object))
    {
        return this;
    }
    switch (BsonType)
    {
        case BsonType.Boolean: return Convert.ChangeType(this.AsBoolean, conversionType, provider);
        case BsonType.DateTime: return Convert.ChangeType(this.ToUniversalTime(), conversionType, provider);
        case BsonType.Decimal128: return Convert.ChangeType(this.AsDecimal128, conversionType, provider);
        case BsonType.Double: return Convert.ChangeType(this.AsDouble, conversionType, provider);
        case BsonType.Int32: return Convert.ChangeType(this.AsInt32, conversionType, provider);
        case BsonType.Int64: return Convert.ChangeType(this.AsInt64, conversionType, provider);
        case BsonType.ObjectId: return Convert.ChangeType(this.AsObjectId, conversionType, provider);
        case BsonType.String: return Convert.ChangeType(this.AsString, conversionType, provider);
        default: throw new InvalidCastException();
    }
}

The version of MongoDb.Bson is 2.17.0

 

We are using MongoDB Community edition version 6.0.0

How to Reproduce

Decrypt the value from the db: (CompanyName is BsonDocument)

 

decodedWorkflow.CompanyName = (await clientEncryption.DecryptAsync(workflow.CompanyName)).AsBsonDocument;

When we check the type of decodedWorkflow.CompanyName, it is RawBsonDocument. The only way to get it to work as BsonDocument is either to convert it to string or to use:

BsonDocument.Parse(JsonConvert.SerializeObject(BsonTypeMapper.MapToDotNetValue(await clientEncryption.DecryptAsync(workflow.CustomFields))));

When the type of this field is BsonDocument (instead RawBsonDocument), then the serialization works. 

I am not sure, if the type should be RawBsonDocument, but then this method: IConvertible.ToType should support Nullable type?

 

Additional Background

 



 Comments   
Comment by James Kovacs [ 04/Nov/22 ]

Hi, simonak@stacc.com,

Thank you for reporting this bug. We have reproduced the issue and it happens for BsonType.Null, BsonType.Binary, BsonType.Timestamp, and others whose behaviour isn't defined in IConvertible.ToType. Note that it does not include BsonType.Array, nor BsonType.Document presumably because Newtonsoft.Json handles those container types differently.

Note that using bsonDocument.ToJson() does not suffer from this issue because it uses MongoDB.Bson's JSON serialization infrastructure.

The following repro demonstrates the issue:

using System;
using System.IO;
using MongoDB.Bson;
using Newtonsoft.Json;
 
BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3;
 
var jsonSerializer = new JsonSerializer();
var bson = new BsonDocument
{
    { "array", new BsonArray { 1, 2, 3 } }, // works
    { "null", BsonNull.Value },
    { "timestamp", BsonTimestamp.Create(42L)},
    { "binary", new BsonBinaryData(Guid.NewGuid(), GuidRepresentation.Standard) }
};
Console.WriteLine(bson.ToJson());
 
using var stringWriter = new StringWriter();
jsonSerializer.Serialize(stringWriter, bson);
Console.WriteLine(stringWriter.ToString());

The list of BsonTypes that must be handled is not exhaustive. BsonType.MinKey, BsonType.MaxKey, and others will exhibit the same behaviour. We should verify that all possible BsonTypes are serializable by Newtonsoft.Json.

To work around this issue now, you could implement your own TextOutputFormatter, which uses bsonDocument.ToJson() to render the JSON string.

Sincerely,
James

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