Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.NET cast error when serializing Mongo ObjectId

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);
    }
like image 289
Keeno Avatar asked May 20 '13 14:05

Keeno


3 Answers

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; }
}
like image 140
Andrew Chaa Avatar answered Oct 20 '22 01:10

Andrew Chaa


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


}
like image 32
Keeno Avatar answered Oct 20 '22 00:10

Keeno


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; }
like image 22
ZOXEXIVO Avatar answered Oct 19 '22 23:10

ZOXEXIVO