JSON.NET cast error when serializing Mongo ObjectId
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;
}
}
Mongo C# Driver and ObjectID JSON String Format in .NET Core
Use BsonDocument when saving to MongoDB
After trying a number of different configurations, the only way I was able to correctly save truly dynamic documents using the connector was to parse objects as BsonDocument
s.
public ActionResult Post([FromBody]JObject resource)
{
var document = BsonDocument.Parse(resource.ToString(Formatting.None));
DbContext.Resources.InsertOne(document);
}
Register BsonDocument
serializers with JSON.Net
The problem with the above approach initially was that when calling ToJson()
the ISODate
and ObjectId
objects would be serialized into objects, which was undesirable. At the time of writing, there doesn't seem to be any extensibility points for overriding this behavior. The logic is baked into the MongoDB.Bson.IO.JsonWriter
class, and you cannot register BsonSerializer
s for BsonValue
types:
MongoDB.Bson.BsonSerializationException: A serializer cannot be registered for type BsonObjectId because it is a subclass of BsonValue.
At the time of writing, the only solution I've found is to explicitly custom JSON.Net converters. MongoDB C# Lead Robert Stam has created an unpublished library for this which community member Nathan Robinson has ported to .net-core.. I've created a fork that properly serializes the ObjectId and ISODate fields.
I've created a NuGet package from their work. To use it, include the following reference in your .csproj
file:
<PackageReference Include="MongoDB.Integrations.JsonDotNet" Version="1.0.0" />
Then, explicitly register the converters:
Startup.cs
using MongoDB.Integrations.JsonDotNet.Converters;
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(options =>
{
// Adds automatic json parsing to BsonDocuments.
options.SerializerSettings.Converters.Add(new BsonArrayConverter());
options.SerializerSettings.Converters.Add(new BsonMinKeyConverter());
options.SerializerSettings.Converters.Add(new BsonBinaryDataConverter());
options.SerializerSettings.Converters.Add(new BsonNullConverter());
options.SerializerSettings.Converters.Add(new BsonBooleanConverter());
options.SerializerSettings.Converters.Add(new BsonObjectIdConverter());
options.SerializerSettings.Converters.Add(new BsonDateTimeConverter());
options.SerializerSettings.Converters.Add(new BsonRegularExpressionConverter());
options.SerializerSettings.Converters.Add(new BsonDocumentConverter());
options.SerializerSettings.Converters.Add(new BsonStringConverter());
options.SerializerSettings.Converters.Add(new BsonDoubleConverter());
options.SerializerSettings.Converters.Add(new BsonSymbolConverter());
options.SerializerSettings.Converters.Add(new BsonInt32Converter());
options.SerializerSettings.Converters.Add(new BsonTimestampConverter());
options.SerializerSettings.Converters.Add(new BsonInt64Converter());
options.SerializerSettings.Converters.Add(new BsonUndefinedConverter());
options.SerializerSettings.Converters.Add(new BsonJavaScriptConverter());
options.SerializerSettings.Converters.Add(new BsonValueConverter());
options.SerializerSettings.Converters.Add(new BsonJavaScriptWithScopeConverter());
options.SerializerSettings.Converters.Add(new BsonMaxKeyConverter());
options.SerializerSettings.Converters.Add(new ObjectIdConverter());
});
}
}
Now, you can serialize using the default serializer:
return Created($"resource/{document["_id"].ToString()}", document);
Automatically retun mongodb ObjectId as string with Json.NET in MVC
Figured a solution, hope it will help someone.
Basically in the controller instead of returning MVC's JsonResult, I returned the Newtonsoft's JObject.
My class looks like this:
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class StubClass
{
[JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id { get; set; }
}
The JsonConverter class looks like this:
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public 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)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}
public override bool CanConvert(Type objectType)
{
return typeof(ObjectId).IsAssignableFrom(objectType);
//return true;
}
}
And the controller:
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
[HttpGet]
public JObject Index()
{
StubClass c = new StubClass()
{
Id = ObjectId.GenerateNewId()
};
JObject jobj = JObject.FromObject(c);
return jobj;
}
Mongo DB object Id deserializing using JSON serializer
You are getting this error because the value for the _id
property does not conform to the JSON standard (see JSON.org). JSON values must be one of the following:
- a string (starts and ends with quote marks
"
) - a number
- an object (starts and ends with curly braces
{
and}
) - an array (starts and ends with square brackets
[
and]
) - the keywords
true
,false
, ornull
The value ObjectId("5378f94a3513fa3374be7e20")
appears to be a function, which is not valid. The value ISODate("2014-05-18T18:17:46.983Z")
has the same problem. You will need to somehow change your JSON to meet the standard if you want to parse it using JSON.net.
JSON.NET cast error when serializing Mongo ObjectId
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;
}
}
Json.NET error handling fails when serializing to specific object
You need to call
args.ErrorContext.Handled = true;
in your callback to tell Json.NET that you handled the exception. If you don't (maybe because you just want to log the error), the exception is thrown after your callback.
Unable to cast object of type 'MongoDB.Bson.Serialization.Serializers.DateTimeSerializer' to type 'MongoDB.Bson.Serialization.IBsonSerializer`
You shouldn't need to perform your own conversion the way you are.
Try setting the BsonRepresentation on your model
public class Model
{
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
[BsonRepresentation(BsonType.DateTime)]
public DateTime startDate { get; set; }
}
For the sake of clarify can i suggest you rename your collection variable to collection instead of model as this in confusing, so
IMongoCollection<Model> collection = dbHelper.GetCollection<Model>(Model.CollectionName);
Model objModel = new Model { startDate = DateTime.UtcNow };
collection.InsertOne(yourModel);
And to perform your update you won't need to set to BsonValue
var query = Builders<Model>.Filter.Eq(t => t.Id, ObjectId.Parse(id));
var update = Builders<Model>.Update.Set(t => t.startdate, objModel.startdate);
var result = model.UpdateOne(query, update);
One suggestion, is to store your ObjectId as string and use BsonRepresentation as you have with DateTime:
[BsonRepresentation(BsonType.ObjectId)]
[BsonId]
public string Id { get; set; }
That way you don't need to go out of your way to parse it to ObjectId and it works the same, so your filter becomes
var query = Builders<Model>.Filter.Eq(t => t.Id, id);
Or you could use Lambda directly in your update like so:
var update = Builders<Model>.Update
.Set(t => t.Id == id, objModel.startdate);
Related Topics
Passing an Enum Value as Command Parameter from Xaml
Weak Event Handler Model for Use with Lambdas
Datetime Format to SQL Format Using C#
How to Tell When Httpclient Has Timed Out
How to Replace Part of String by Position
ASP.NET Identity Get All Roles of Logged in User
Differencebetween Casting and Conversion
Check If Property Has Attribute
C#:Is Variance (Covariance/Contravariance) Another Word for Polymorphism
How to Correctly Prefix a Word with "A" and "An"