38

I am playing around with MongoDB and have an object with a mongodb ObjectId on it. When I serialise this with the .NET Json() method, all is good (but the dates are horrible!)

If I try this with the JSON.NET serialiser it gives me an InvalidCastException when trying to serialise the ObjectID

any ideas whats happening and how I can fix this?

using MongoDB.Driver;
using MongoDB.Bson;
using Newtonsoft.Json;

//this is a route on a controller
   public string NiceJsonPlease()
    {

        var q = new TestClass();
        q.id = new ObjectId();
        q.test = "just updating this";

        return JsonConvert.SerializeObject(q);
    }

    //simple test class
    class TestClass
    {
        public ObjectId id; //MongoDB ObjectID
        public string test = "hi there";
    }


Exception Details: System.InvalidCastException: Specified cast is not valid.

If you change the controller method to use the serializer that ships with .NET, it works ok (but, this one gives ugly dates, blugh)

public JsonResult NiceJsonPlease()
    {

        var q = new TestClass();
        q.id = new ObjectId();
        q.test = "just updating this";

        return Json(q, JsonRequestBehavior.AllowGet);
    }
Keeno
  • 1,626
  • 2
  • 18
  • 25
  • 1
    ah, the old Json dates issue: http://www.hanselman.com/blog/OnTheNightmareThatIsJSONDatesPlusJSONNETAndASPNETWebAPI.aspx – Liam May 20 '13 at 14:32
  • @Liam hi Liam, I actually read that blog earlier today and is sort of the reason I am asking this question. JSON.NET gives me the nicer date but if I include the MongoDB ObjectID in my model and try and serialize it with JSON.NET, it errors. – Keeno May 20 '13 at 14:57
  • 1
    Can you show your code? – Liam May 20 '13 at 15:03
  • I had similar problem when use atribute [BsonDefaultValue(0)] over property and use ProjectionDefinition to get data form MongoDB [BsonDefaultValue(0)] public long SomePropery; Solution is remove atribute. – Stanislav Prusac Feb 21 '16 at 17:39

7 Answers7

63

You can use .NET string type instead of ObjectId, You just need to decorate it with BsonRepresentation. If you use BsonDateTime, you will have the same conversion issue. This is a domain class in my project that uses those decorators.

public class DocumentMetadata
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    public string Name { get; set; }
    public string FullName { get; set; }

    [BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
    public DateTime DownloadTime { get; set; }
}
Andrew Chaa
  • 6,120
  • 2
  • 45
  • 33
32

I had a pointer from the MongoDB user group. https://groups.google.com/forum/?fromgroups=#!topic/mongodb-csharp/A_DXHuPscnQ

The response was

This seems to be a Json.NET issue, but not really. There is a custom type here it simply doesn't know about. You need to tell Json.NET how to serialize an ObjectId.

So, I implemented the following solution

I decorated my ObjectId with

[JsonConverter(typeof(ObjectIdConverter))]

Then wrote a custom converter that just spits out the Guid portion of the ObjectId

 class ObjectIdConverter : JsonConverter
{

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    { 
        serializer.Serialize(writer, value.ToString());
       
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(ObjectId).IsAssignableFrom(objectType);
        //return true;
    }


}
Liam
  • 27,717
  • 28
  • 128
  • 190
Keeno
  • 1,626
  • 2
  • 18
  • 25
  • 2
    This worked wonderfully for me. I found a completed version of a class with the same name here: https://github.com/geersch/MySinglePageApplication/blob/master/MvcApplication/Models/ObjectIdConverter.cs – Doug Johnson Jan 27 '15 at 13:02
  • @Syed Muhammed Mubashir, To make this work, just add a conversion attribute above the ObjectId field. [JsonConverter(typeof(ObjectIdConverter))] public ObjectId SiteUserId { get; set; } – Doug Johnson Mar 08 '15 at 04:36
  • Better way - register converter globally - _serializerSettings = new JsonSerializerSettings() { Converters = new List { new ObjectIdConverter() } }; – ZOXEXIVO Jun 22 '16 at 10:59
  • This pointed me to the right direction, I was experiencing issues in converting BsonDocument to JSON. Big thanks. – Dinei Oct 07 '16 at 14:10
  • @DougJohnson - I tried above solution but i am getting error. I have posted question in https://stackoverflow.com/questions/69446519/mongodb-objectid-conversion-error-in-c-sharp-using-newtonsoft-deserialization – Ronak Oct 05 '21 at 08:27
22
  1. Write ObjectId converter

    public class ObjectIdConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(ObjectId); }

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
     {
         if (reader.TokenType != JsonToken.String)
             throw new Exception($"Unexpected token parsing ObjectId. Expected String, got {reader.TokenType}.");
    
         var value = (string)reader.Value;
         return string.IsNullOrEmpty(value) ? ObjectId.Empty : new ObjectId(value);
     }
    
     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
     {
         if (value is ObjectId)
         {
             var objectId = (ObjectId)value;
             writer.WriteValue(objectId != ObjectId.Empty ? objectId.ToString() : string.Empty);
         }
         else
         {
             throw new Exception("Expected ObjectId value.");
         }
     }
    

    }

  2. Register it in JSON.NET globally with global settings and you not need mark you models with big attributes

             var _serializerSettings = new JsonSerializerSettings()
             {
                 Converters = new List<JsonConverter> { new ObjectIdConverter() }
             };
    
  3. Big advice - don't use ObjectId in your models - use string

    [BsonRepresentation(BsonType.ObjectId)] public string Id{ get;set; }

Hassan Rahman
  • 4,953
  • 1
  • 34
  • 32
ZOXEXIVO
  • 920
  • 9
  • 21
  • Can you also please list which imports you used. I'm assuming: using System; using MongoDB.Bson; using Newtonsoft.Json; – Jack Feb 25 '20 at 11:57
10

I resolved a similar problem I was experiencing with the JSON.NET serializer/InvalidCastException error by setting the JsonOutputMode to strict, which eradicated the need to change the underlying type:

var jsonWriterSettings = new JsonWriterSettings { OutputMode = JsonOutputMode.Strict };
var json = doc.ToJson(jsonWriterSettings);

With further information available in the API: http://api.mongodb.org/csharp/1.8.3/html/d73bf108-d68c-e472-81af-36ac29ea08da.htm

lintunen
  • 131
  • 1
  • 6
2

I ran into a similar problem with a Web API project, and wound up beating my head against the keyboard for a few hours before I found this thread.

Initially everything was working fine, but then I ran into the problem after converting my code to use my own custom class instead of the BsonDocument object as recommended in the mongoDB C# driver documentation.

http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-csharp-driver/#bsondocument-object-model-vs-your-own-domain-classes

Here the VB.net equivalent to the solution above for those that need it;

Public Class DocumentMetadata
    <BsonId> _
    <BsonRepresentation(BsonType.ObjectId)> _
    Public Property Id() As String
    Public Property Name() As String
    Public Property FullName() As String

    <BsonDateTimeOptions(Kind := DateTimeKind.Utc)> _
    Public Property DownloadTime() As DateTime
End Class
Drifter
  • 21
  • 1
1

There is an another option

object dotnetObject = BsonTypeMapper.MapToDotNetValue(bsonDocument);

// Json mapped to default .Net objects
string json = Newtonsoft.Json.JsonConvert.SerializeObject(dotnetObject);

// Parsing as JObject
var jobject = JObject.Parse(json);

// Deserializing as your custom Type
var myObject = JsonConvert.DeserializeObject<MyType>(json);
Murilo Maciel Curti
  • 2,677
  • 1
  • 21
  • 26
  • 1
    It worked for converting MongoDBRealm objects in Xamarin.iOS. Had to convert the RealmObject into a BsonDocument first. – sandeepani Oct 15 '21 at 05:36
0

I used this code in VB.Net and worked perfect, you can see the objectId in the class and you can do the same thing with the data type DATE.

    Imports MongoDB.Bson
    Imports MongoDB.Bson.Serialization.Attributes    
    Imports MongoDB.Driver

Public Class _default
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim objMongo As New MongoClient("mongodb://192.168.111.5:27017")
        Dim objDatabase As IMongoDatabase = objMongo.GetDatabase("local")
        Dim objCollection = objDatabase.GetCollection(Of BsonDocument)("Test")            
        Dim _ret As New List(Of mongo_users)

        Dim result = objCollection.Find(New BsonDocument()).ToList()
        Dim _json_response = result.ToJson()
        If _json_response <> "" Then

            _ret = MongoDB.Bson.Serialization.BsonSerializer.Deserialize(Of List(Of mongo_users))(_json_response)

        End If

        For Each item In _ret
            Response.Write(item.name & " " & item.last_name & "</br>")
        Next


    End Sub

End Class

Public Class mongo_users            
    <BsonId>
    <BsonRepresentation(BsonType.ObjectId)>
    Public Property _id() As String
    Public Property status As Integer
    Public Property name As String
    Public Property last_name As String
    Public Property colors As List(Of user_colors)    
End Class

Public Class user_colors
    Public Property color_name As String
End Class
Diego
  • 659
  • 5
  • 5