Serializing ObjectID with JSON.NET fails with a cast error

8,352 views
Skip to first unread message

Chris Johnson

unread,
May 22, 2013, 6:54:40 AM5/22/13
to mongodb...@googlegroups.com
Hi there
I have posted on Stack also, but have not had much luck there.

I also *think* I posted here, but the forum seemed to swallow my question

The Problem: If I try and serialise ANY class that has an objectID on it using JSON.NET I get an exception.

Here is the simple code


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

            var q = new TestClass();
            q.id = new ObjectId("519a4d5abb9b41199cee0264");
            q.test = "just updating this";
            
            return JsonConvert.SerializeObject(q);

        }

        class TestClass            
        {
            public ObjectId id;
            public string test = "hi there";
        }


I will post the stack trace below,

thanks very much for your help and time

Chris Johnson

unread,
May 22, 2013, 6:55:09 AM5/22/13
to mongodb...@googlegroups.com

Specified cast is not valid.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

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

Source Error: 

Line 31:           
Line 32: 
Line 33:             return JsonConvert.SerializeObject(q);
Line 34:                 //(q, JsonRequestBehavior.AllowGet);
Line 35:         }

Source File: D:\Projects\Question Bank Service\Trunk\QuestionBankAdmin\Controllers\HomeController.cs    Line: 33 

Stack Trace: 

[InvalidCastException: Specified cast is not valid.]
   MongoDB.Bson.ObjectId.System.IConvertible.ToType(Type conversionType, IFormatProvider provider) +347
   Newtonsoft.Json.JsonWriter.WriteValue(JsonWriter writer, PrimitiveTypeCode typeCode, Object value) +3733
   Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializePrimitive(JsonWriter writer, Object value, JsonPrimitiveContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) +129
   Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) +429
   Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) +342
   Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty) +258
   Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) +107
   Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType) +774
   Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, Formatting formatting, JsonSerializerSettings settings) +163
   Newtonsoft.Json.JsonConvert.SerializeObject(Object value) +11
   QuestionBankAdmin.Controllers.HomeController.NiceJsonPlease() in D:\Projects\Question Bank Service\Trunk\QuestionBankAdmin\Controllers\HomeController.cs:33
   lambda_method(Closure , ControllerBase , Object[] ) +62
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +59
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +435
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +60
   System.Web.Mvc.Async.AsyncControllerActionInvoker.InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +50
   System.Web.Mvc.Async.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41() +75
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +44
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +102
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +73
   System.Web.Mvc.Async.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33() +126
   System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +323
   System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult) +44
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +102
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +74
   System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +68
   System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +184
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +136
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +56
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +66
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult) +40
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +68
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +65
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +45
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +66
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +931
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +188

craiggwilson

unread,
May 22, 2013, 8:23:00 AM5/22/13
to mongodb...@googlegroups.com
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.

Daniel Harman

unread,
May 22, 2013, 9:37:35 AM5/22/13
to mongodb...@googlegroups.com

This might come back to the type converter I failed to pull request without breaking the line endings!!

Dan

--
You received this message because you are subscribed to the Google Groups "mongodb-csharp" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mongodb-cshar...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Chris Johnson

unread,
May 22, 2013, 9:50:14 AM5/22/13
to mongodb...@googlegroups.com

craiggwilson

unread,
May 22, 2013, 11:59:46 AM5/22/13
to mongodb...@googlegroups.com
Yes, Daniel, that may or may not have fixed this.  Did you put a Jira in for that?  If not, feel free to open one so this doesn't fall off the radar if it is still important to you.

@Chris, glad that worked.  I didn't think it'd be that difficult.

Lajos Marton

unread,
Jul 8, 2013, 10:25:18 AM7/8/13
to mongodb...@googlegroups.com
Hi,

This is a quite serious issue. I think more and more developer will using MongoDb and Json.Net. I ran into this error too.

Khalid Salomão

unread,
Sep 17, 2013, 6:27:14 PM9/17/13
to mongodb...@googlegroups.com
HI,

Any progress on this issue?

Regards,

craiggwilson

unread,
Sep 17, 2013, 8:03:21 PM9/17/13
to mongodb...@googlegroups.com
To what issue are your referring?  Chris posted a solution on StackOverflow here: http://stackoverflow.com/questions/16651776/json-net-cast-error-when-serializing-mongo-objectid/16693462#16693462.

James Newton-King

unread,
Oct 1, 2013, 6:46:46 PM10/1/13
to mongodb...@googlegroups.com
No, the solution is to fix the problem in the Mongo C# driver so you don't force people to, 1) encounter the problem, 2) have to search for a solution, 3) add a workaround to their code.

Robert Stam

unread,
Oct 1, 2013, 10:12:58 PM10/1/13
to mongodb...@googlegroups.com
Hi James,

I've reproduced this using the latest version of Json.NET from nuget and the just released v1.8.3 of the C# driver.

It turns out that Json NET is calling the explicit implementation of IConvertible.ToType in class ObjectId with a conversionType equal to System.Object, which we were not expecting and which results in the C# driver throwing an InvalidCastException. I've created a CSHARP ticket for this issue:


However, after changing the C# driver's implementation of ToType to support type object, I now get a stack overflow in Json.NET when attempting to serialize the TestClass.

I've isolated the issue below so that it can be reproduced using only Json NET. The call to JsonConvert.SerializeObject results in a StackOverflowException being thrown. Perhaps you could take a look at this from the Json.NET side and help me figure out if I'm doing something wrong.

Thanks for your help!

Robert

using System;
using Newtonsoft.Json;

namespace TestJsonNetWithObjectId
{
    public class ConvertibleId : IConvertible
    {
        public int Value;

        TypeCode IConvertible.GetTypeCode() { return TypeCode.Object; }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            if (conversionType == typeof(object)) { return this; }
            if (conversionType == typeof(int)) { return (int)Value; }
            if (conversionType == typeof(long)) { return (long)Value; }
            throw new InvalidCastException();
        }

        bool IConvertible.ToBoolean(IFormatProvider provider) { throw new InvalidCastException(); }
        byte IConvertible.ToByte(IFormatProvider provider) { throw new InvalidCastException(); }
        char IConvertible.ToChar(IFormatProvider provider) { throw new InvalidCastException(); }
        DateTime IConvertible.ToDateTime(IFormatProvider provider) { throw new InvalidCastException(); }
        decimal IConvertible.ToDecimal(IFormatProvider provider) { throw new InvalidCastException(); }
        double IConvertible.ToDouble(IFormatProvider provider) { throw new InvalidCastException(); }
        short IConvertible.ToInt16(IFormatProvider provider) { return (short)Value; }
        int IConvertible.ToInt32(IFormatProvider provider) { return Value; }
        long IConvertible.ToInt64(IFormatProvider provider) { return (long)Value; }
        sbyte IConvertible.ToSByte(IFormatProvider provider) { throw new InvalidCastException(); }
        float IConvertible.ToSingle(IFormatProvider provider) { throw new InvalidCastException(); }
        string IConvertible.ToString(IFormatProvider provider) { throw new InvalidCastException(); }
        ushort IConvertible.ToUInt16(IFormatProvider provider) { throw new InvalidCastException(); }
        uint IConvertible.ToUInt32(IFormatProvider provider) { throw new InvalidCastException(); }
        ulong IConvertible.ToUInt64(IFormatProvider provider) { throw new InvalidCastException(); }
    }

    public class TestClass
    {
        public ConvertibleId Id;
        public int X;
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            var c = new TestClass { Id = new ConvertibleId { Value = 1 }, X = 2 };
            var s = JsonConvert.SerializeObject(c); // throws a StackOverflowException
        }
    }
}



--

Robert Stam

unread,
Oct 2, 2013, 1:07:40 AM10/2/13
to mongodb...@googlegroups.com
I think Json.NET doesn't work with user written classes that implement IConvertible. Or at the very least it must be making some assumptions about IConvertible that aren't being met in this sample code.

Json.NET serializes the above sample classes (TestClass and ConvertibleId) just fine if I remove the implementation of IConvertible from the ConvertibleId class.

The way this relates to the original question is that the ObjectId class in the C# driver also implements IConvertible.

James Newton-King

unread,
Oct 2, 2013, 6:15:18 PM10/2/13
to mongodb...@googlegroups.com
I've logged a bug about it too - https://github.com/JamesNK/Newtonsoft.Json/issues/138

I'll look at this in more detail later but I think the solution might be for Json.NET to be updated to ask for a typeof(string) if the value that implements IConvertible has a type code of Object.

The other option is for the MongoDB C# driver for ObjectID to return TypeCode.String.

craiggwilson

unread,
Oct 3, 2013, 9:54:46 AM10/3/13
to mongodb...@googlegroups.com
Returning TypeCode of string seems wrong.  In addition, Int32, for example, returns itself when the target type is object.  I attempted to implement a TypeConverter for ObjectId and this worked remarkably well when serializing the object.  However, Json.NET did not use the type converter when deserializing the object.  In the below program using a modified version of ObjectId with the TypeConverter, the ObjectId was serialized to a string, but the round-trip failed stating that string couldn't be converted to an ObjectId.  (In a related note, MVC and WebApi use the TypeConverter on the way in for parameter binding, but then serialize the ObjectId without it.)

        private class Test
        {
            [JsonProperty()]
            public ObjectId Id { get; set; }
        }

        static void Main(string[] args)
        {
            var test = new Test 
            { 
                Id = ObjectId.GenerateNewId()
            };

            var json = JsonConvert.SerializeObject(test);

            var andBack = JsonConvert.DeserializeObject(json, typeof(Test));

Ken Egozi

unread,
Oct 3, 2013, 10:39:43 AM10/3/13
to mongodb-csharp
Regarding roundtrip, and also to support deserialization to untyped object (dynamic, or even  non .NET, e.g. sending documents to the rest api of mongo, e.g. at MongoLab) , shouldn't the ObjectId be serialized to the form of { $oid : "....." }  anyway?

Pseudo test code would be:
var test = ...
var json = JsonConvert.SerializeObject(test);
dynamic andBack = JObject.Parse(json);

Assert.Equals(andBack.Id.GetType() , typeof(ObjectId));

 

bakku pavan

unread,
Oct 4, 2013, 1:00:56 AM10/4/13
to mongodb...@googlegroups.com
Hi,
   I have done that displayed mongodb database _id in jqgrid(type of gridview) by the following casting : 

using MongoDB.Bson.Serialization.Attributes;  //NAMESPACE

[BsonRepresentation(BsonType.ObjectId)]
        public string _id { get; set; }                    //DECLARATION OF VARIABLE AS STRING in Product class


---------------------CASTING--------------------------
var jsonSerializer = new JavaScriptSerializer();
context.Response.Write(jsonSerializer.Serialize(collectionEmployee.AsQueryable<Product>().ToList<Product>()));


Hope this is help full.

James Newton-King

unread,
Oct 4, 2013, 5:33:03 AM10/4/13
to mongodb...@googlegroups.com
ObjectIds will now successfully serialize.

Note that they still won't deserialize because IConvertible is a horrible interface.

James Newton-King

unread,
Oct 4, 2013, 5:33:40 AM10/4/13
to mongodb...@googlegroups.com
(after the next release - 5.0.7?)
Reply all
Reply to author
Forward
0 new messages