My entity class is like this:
public class MyType
{
public string Name { get; set; }
public IMongoQuery MyQuery { get; set; }
}
I am not able to persist this with the default serializers, when MyQuery contains anything complex such as an $in.
BsonDocumentSerializer gives error:
Element name '$in' is not valid because it starts with a '$'.
I assume I need a special serializer type attributed to MyQuery. I've tried BsonDocument, BsonString, BsonJavaScript - all cannont cast to MongoDB.Driver.QueryDocument, which is the type of object stored in MyQuery.
Does this require a custom IBsonSerializer?
Works for C# driver 2.0:
public class MyIMongoSerializer : SerializerBase<IMongoQuery>
{
public override void Serialize(BsonSerializationContext context,
BsonSerializationArgs args,
IMongoQuery value)
{
if (value == null)
{
context.Writer.WriteNull();
}
else
{
var query = (IMongoQuery)value;
var json = query.ToJson();
context.Writer.WriteString(json);
}
}
public override IMongoQuery Deserialize(BsonDeserializationContext context,
BsonDeserializationArgs args)
{
if (context.Reader.GetCurrentBsonType() == BsonType.Null)
{
context.Reader.ReadNull();
return null;
}
else
{
var value = context.Reader.ReadString();
var doc = BsonDocument.Parse(value);
var query = new QueryDocument(doc);
return query;
}
}
}
And annotate the property to use the serializer:
[BsonSerializer(typeof(MyIMongoSerializer))]
public IMongoQuery filter { get; set; }
This seems to work. Stores the query as a JSON string.
public class QueryDocumentSerializer : BsonBaseSerializer
{
public override object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
if (bsonReader.GetCurrentBsonType() == BsonType.Null)
{
bsonReader.ReadNull();
return null;
}
else
{
var value = bsonReader.ReadString();
var doc = BsonDocument.Parse(value);
var query = new QueryDocument(doc);
return query;
}
}
public override void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
if (value == null)
{
bsonWriter.WriteNull();
}
else
{
var query = (QueryDocument)value;
var json = query.ToJson();
bsonWriter.WriteString(json);
}
}
}
Related
I create a class for define my request, I don't get the accepted JSON string
I define this object:
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
when I convert it to json, I get the following string:
{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}
how can I define the class, for get the accepted json string:
{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}
You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.
public class PropertyAsObjectConverter : JsonConverter
{
private readonly Type[] _types;
public PropertyAsObjectConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var name = property.Name;
var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
if(attrs != null)
{
if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
name = attr.PropertyName;
}
writer.WriteStartObject();
writer.WritePropertyName(name);
serializer.Serialize(writer, property.GetValue(value));
writer.WriteEndObject();
}
}
}
This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.
You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.
public class Content
{
[JsonProperty("requests")]
public Request Value { get; set; }
}
public class Request
{
[JsonProperty("var_args")]
public VarArgs[] Arguments { get; set; }
}
public class VarArgs
{
[JsonProperty("title")]
public object Title { get; set; }
[JsonProperty("owner")]
public object Owner { get; set; }
}
This is how you can use it:
static void Main(string[] args)
{
var request = new Content()
{
Value = new Request()
{
Arguments = new VarArgs[]
{
new VarArgs()
{
Title = "Test",
Owner = "Skaner",
}
}
}
};
var text = JsonConvert.SerializeObject(
request,
Formatting.None,
new PropertyAsObjectConverter(typeof(VarArgs)));
Console.WriteLine(text);
}
The output for this sample is the one you expect:
{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}
You could use a custom JsonConverter like the below.
It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.
public class VarArgsConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = (JObject)JToken.FromObject(value);
var objTitle = new JObject();
objTitle.Add("title", obj.GetValue("title"));
var objOwner = new JObject();
objOwner.Add("owner", obj.GetValue("owner"));
objTitle.WriteTo(writer);
objOwner.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Var_Args);
}
}
public class Wrapper
{
[JsonProperty("requests")]
public Request Requests { get; set; }
}
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
Then use it:
var wrapper = new Wrapper();
var request = new Request();
request.var_args = new Var_Args[] {
new Var_Args(){ title = "Test", owner = "skaner" },
new Var_Args(){ title = "Test2", owner = "skaner2" }
};
wrapper.Requests = request;
var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());
Output
{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}
Note: I'm using the Wrapper class just to produce the requested JSON.
If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.
If you register the JsonConvert globally you can use:
var serialized = JsonConvert.SerializeObject(wrapper);
You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Entry = new Dictionary<string,object>();
Entry.Add(Property.Name, Property.GetValue(this));
yield return Entry;
}
}
public object title { get; set; }
public object owner { get; set; }
}
While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
private struct PropertyList<T>
{
public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;
static PropertyList()
{
PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Args = new [] { Expression.Parameter(typeof(T)) };
var Key = Property.Name;
var Value = Expression.Property(Args[0], Property);
Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();
PropertyGetters.Add(obj =>
{
var entry = new Dictionary<string,object>();
entry.Add(Key, Get(obj));
return entry;
});
}
}
}
protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
{
return PropertyList<T>.PropertyGetters.Select(f => f(obj));
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
return GetPropertiesAsEntries(this).GetEnumerator();
}
public object title { get; set; }
public object owner { get; set; }
}
I want to serialize in MongoDB, member Engines (HashSet of enum) as string to see the name as string and not a number
The class is :
public class EnginesPerFile
{
public Guid FileId { get; set; }
public string Path { get; set; }
public HashSet<EngineType> Engines { get; set; }
}
I am trying to map like this :
BsonClassMap.RegisterClassMap<EnginesPerFile>(cm =>
{
cm.AutoMap();
cm.MapIdField(c => c.FileId);
cm.MapMember(c => c.Engines).SetSerializer(new EnumSerializer<EngineType>(BsonType.String));
});
But i get an error:
Value type of serializer is EngineType and does not match member type System.Collections.Generic.HashSet`1[[Playground.Model.EngineType, Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
Parameter name: serializer.
How can i serialize hashset of enum in MongoDB?
I fix it by writing custom Serializer: (EngineType can be a generic T)
public class MyHashSetSerializer : SerializerBase<HashSet<EngineType>>
{
private readonly IBsonSerializer _serializer =
BsonSerializer.LookupSerializer(typeof(EngineType));
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, HashSet<EngineType> value)
{
var set = (HashSet<EngineType>)value;
context.Writer.WriteStartDocument();
int i = 0;
foreach (var element in set)
{
context.Writer.WriteName($"[{i}]");
context.Writer.WriteString(element.ToString());
}
context.Writer.WriteEndDocument();
}
public override HashSet<EngineType> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var set = new HashSet<EngineType>();
context.Reader.ReadStartDocument();
while (context.Reader.ReadBsonType() != 0)
{
context.Reader.SkipName();
var element =
(EngineType)_serializer.Deserialize(context, args);
set.Add(element);
}
context.Reader.ReadEndDocument();
return set;
}
}
And then i register class map like this:
BsonClassMap.RegisterClassMap<EnginesPerFile>(cm =>
{
cm.AutoMap();
cm.MapIdField(c => c.FileId);
cm.MapMember(c => c.Engines).SetSerializer(new MyHashSetSerializer());
});
how can I deserialize below json structure using newtonsoft json.net in .net.
{
"users" : {
"parentname":"test",
"100034" : {
"name" : "tom",
"state" : "WA",
"id" : "cedf-c56f-18a4-4b1"
},
"10045" : {
"name" : "steve",
"state" : "NY",
"id" : "ebb2-92bf-3062-7774"
},
"12345" : {
"name" : "mike",
"state" : "MA",
"id" : "fb60-b34f-6dc8-aaf7"
}
}
}
I tried below code but its not working. I got error 'Error converting value "test" to type 'ConsoleApplication2.User'. Path 'users.parentname', line 5, position 35.'
class Program
{
static void Main(string[] args)
{
string json = #"
{
""users"": {
""parentname"":""test"",
""10045"": {
""name"": ""steve"",
""state"": ""NY"",
""id"": ""ebb2-92bf-3062-7774""
}
}
}";
RootObject root = JsonConvert.DeserializeObject<RootObject>(json);
}
}
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Please suggest.
You have a couple problems:
Your JSON has an extra level of nesting, with the root object containing a single property "users":
{
"users" : { ... }
}
Your data model needs to reflect this.
Your "users" object has a mixture of known and unknown property names. The question Deserialize json with known and unknown fields addresses a similar situation, however in your case your unknown properties always have a fixed schema and their values should be deserialized into a dictionary of POCOs -- specifically the User class. Therefore the answers there don't quite meet your needs, nor does the build-in functionality [JsonExtensionData].
The following converter allows for unknown properties to be deserialized into a typed container, rather than into an dictionary of arbitrary types:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonTypedExtensionDataAttribute : Attribute
{
}
public class TypedExtensionDataConverter<TObject> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TObject).IsAssignableFrom(objectType);
}
JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
{
try
{
return contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any()).Single();
}
catch (InvalidOperationException ex)
{
throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var extensionJsonProperty = GetExtensionJsonProperty(contract);
var extensionJProperty = (JProperty)null;
for (int i = jObj.Count - 1; i >= 0; i--)
{
var property = (JProperty)jObj.AsList()[i];
if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
{
if (extensionJProperty == null)
{
extensionJProperty = new JProperty(extensionJsonProperty.PropertyName, new JObject());
jObj.Add(extensionJProperty);
}
((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
}
}
var value = existingValue ?? contract.DefaultCreator();
using (var subReader = jObj.CreateReader())
serializer.Populate(subReader, value);
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var extensionJsonProperty = GetExtensionJsonProperty(contract);
JObject jObj;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
jObj = JObject.FromObject(value, serializer);
}
var extensionValue = (jObj[extensionJsonProperty.PropertyName] as JObject).RemoveFromLowestPossibleParent();
if (extensionValue != null)
{
for (int i = extensionValue.Count - 1; i >= 0; i--)
{
var property = (JProperty)extensionValue.AsList()[i];
jObj.Add(property.RemoveFromLowestPossibleParent());
}
}
jObj.WriteTo(writer);
}
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
public static class JsonExtensions
{
public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
public static IList<JToken> AsList(this IList<JToken> container) { return container; }
}
Then use it in your classes as follows:
class RootObject
{
[JsonProperty("users")]
public Users Users { get; set; }
}
[JsonConverter(typeof(TypedExtensionDataConverter<Users>))]
class Users
{
public Users()
{
this.UserTable = new Dictionary<string, User>();
}
[JsonProperty("parentname")]
public string ParentName { get; set; }
[JsonTypedExtensionData]
public Dictionary<string, User> UserTable { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
}
I wrote the converter in a fairly general way so it can be reused. A converter that is hardcoded for the Users type would require less code.
Your Json has to look like this:
{
"ParentName":"test",
"users":{
"10045":{
"name":"steve",
"state":"NY",
"id":"ebb2-92bf-3062-7774",
"ParentName":"someOtherName"
}
}
}
In order to deserialize it with your given class structure:
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Now you can deserialize the Json string with:
var root = JsonConvert.DeserializeObject<RootObject>(json);
This is a tough one. I have an issue with binding a model from JSON. I am attempting to resolve polymorphic-ally the record supplied with the type of record that it will resolve to (I want to be able to add many record types in the future). I have attempted to use the following example to resolve my model when calling the endpoint however this example only works for MVC and not Web API applications.
I have attempted to write it using IModelBinder and BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext). However I can't find the equivalent of ModelMetadataProviders in the System.Web.Http namespace.
Appreciate any help anyone can give.
I have a Web API 2 application which has the following object structure.
public abstract class ResourceRecord
{
public abstract string Type { get; }
}
public class ARecord : ResourceRecord
{
public override string Type
{
get { return "A"; }
}
public string AVal { get; set; }
}
public class BRecord : ResourceRecord
{
public override string Type
{
get { return "B"; }
}
public string BVal { get; set; }
}
public class RecordCollection
{
public string Id { get; set; }
public string Name { get; set; }
public List<ResourceRecord> Records { get; }
public RecordCollection()
{
Records = new List<ResourceRecord>();
}
}
JSON Structure
{
"Id": "1",
"Name": "myName",
"Records": [
{
"Type": "A",
"AValue": "AVal"
},
{
"Type": "B",
"BValue": "BVal"
}
]
}
After some research I discovered that metadata providers don't exist within WebAPI and in order to bind to complex abstract objects you have to write your own.
I started by writing a new model binding method, with the use of a custom type name JSon serializer and finally I updated my endpoint to use the custom binder. It's worth noting the following will only work with requests in the body, you will have to write something else for requests in the header. I would suggest a read of chapter 16 of Adam Freeman's Expert ASP.NET Web API 2 for MVC Developers and complex object binding.
I was able to serialize my object from the body of the request using the following code.
WebAPI configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Services.Insert(typeof(ModelBinderProvider), 0,
new SimpleModelBinderProvider(typeof(RecordCollection), new JsonBodyModelBinder<RecordCollection>()));
}
}
Custom model binder
public class JsonBodyModelBinder<T> : IModelBinder
{
public bool BindModel(HttpActionContext actionContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(T))
{
return false;
}
try
{
var json = ExtractRequestJson(actionContext);
bindingContext.Model = DeserializeObjectFromJson(json);
return true;
}
catch (JsonException exception)
{
bindingContext.ModelState.AddModelError("JsonDeserializationException", exception);
return false;
}
return false;
}
private static T DeserializeObjectFromJson(string json)
{
var binder = new TypeNameSerializationBinder("");
var obj = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Binder = binder
});
return obj;
}
private static string ExtractRequestJson(HttpActionContext actionContext)
{
var content = actionContext.Request.Content;
string json = content.ReadAsStringAsync().Result;
return json;
}
}
Custom Serialization binding
public class TypeNameSerializationBinder : SerializationBinder
{
public string TypeFormat { get; private set; }
public TypeNameSerializationBinder(string typeFormat)
{
TypeFormat = typeFormat;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}
public override Type BindToType(string assemblyName, string typeName)
{
string resolvedTypeName = string.Format(TypeFormat, typeName);
return Type.GetType(resolvedTypeName, true);
}
}
End point definition
[HttpPost]
public void Post([ModelBinder(BinderType = typeof(JsonBodyModelBinder<RecordCollection>))]RecordCollection recordCollection)
{
}
The TypeNameSerializationBinder class is not necessary anymore as well as the WebApiConfig configuration.
First, you need to create enum for record type:
public enum ResourceRecordTypeEnum
{
a,
b
}
Then, change your "Type" field in ResourceRecord to be the enum we just created:
public abstract class ResourceRecord
{
public abstract ResourceRecordTypeEnum Type { get; }
}
Now you should create these 2 classes:
Model Binder
public class ResourceRecordModelBinder<T> : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(T))
return false;
try
{
var json = ExtractRequestJson(actionContext);
bindingContext.Model = DeserializeObjectFromJson(json);
return true;
}
catch (JsonException exception)
{
bindingContext.ModelState.AddModelError("JsonDeserializationException", exception);
return false;
}
}
private static T DeserializeObjectFromJson(string json)
{
// This is the main part of the conversion
var obj = JsonConvert.DeserializeObject<T>(json, new ResourceRecordConverter());
return obj;
}
private string ExtractRequestJson(HttpActionContext actionContext)
{
var content = actionContext.Request.Content;
string json = content.ReadAsStringAsync().Result;
return json;
}
}
Converter class
public class ResourceRecordConverter : CustomCreationConverter<ResourceRecord>
{
private ResourceRecordTypeEnum _currentObjectType;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jobj = JObject.ReadFrom(reader);
// jobj is the serialized json of the reuquest
// It pulls from each record the "type" field as it is in requested json,
// in order to identify which object to create in "Create" method
_currentObjectType = jobj["type"].ToObject<ResourceRecordTypeEnum>();
return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
}
public override ResourceRecord Create(Type objectType)
{
switch (_currentObjectType)
{
case ResourceRecordTypeEnum.a:
return new ARecord();
case ResourceRecordTypeEnum.b:
return new BRecord();
default:
throw new NotImplementedException();
}
}
}
Controller
[HttpPost]
public void Post([ModelBinder(BinderType = typeof(ResourceRecordModelBinder<RecordCollection>))] RecordCollection recordCollection)
{
}
Another option if you don't want to create a generic binder.
Custom Binder
public class RecordCollectionModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(RecordCollection))
{
return false;
}
try
{
var json = ExtractRequestJson(actionContext);
bindingContext.Model = DeserializeObjectFromJson(json);
return true;
}
catch (JsonException exception)
{
bindingContext.ModelState.AddModelError("JsonDeserializationException", exception);
return false;
}
}
private string ExtractRequestJson(HttpActionContext actionContext)
{
var content = actionContext.Request.Content;
string json = content.ReadAsStringAsync().Result;
return json;
}
private RecordCollection DeserializeObjectFromJson(string json)
{
var jObject = JObject.Parse(json);
var result = jObject.ToObject<RecordCollection>();
if (result.Restrictions == null)
{
return result;
}
int index = 0;
foreach (var record in result.Records.ToList())
{
switch (record.Type)
{
case "A":
result.Restrictions[index] = jObject["Records"][index].ToObject<ARecord>();
break;
case "B":
result.Restrictions[index] = jObject["Records"][index].ToObject<BRecord>();
break;
}
index++;
}
return result;
}
}
Controller
[HttpPost]
public void Post([ModelBinder(BinderType = typeof(RecordCollectionModelBinder))] RecordCollection recordCollection)
{
}
I am new to using mongo db in C# , but I am trying to import large database in mongo db.
MyDb consists entities having only simple parameters Id , Body , Title Tags.
This is example of entity in mongo.
{
"Id" : "someff asdsa",
"Title" : "fsfds fds",
"Body ": "fsdfsd fs",
"Tags" : "fsdfdsfsd"
}
This is my class of mongoEntity in C#
[BsonIgnoreExtraElements]
class Element
{
[BsonId]
public ObjectId _id { get; set; }
[BsonElement("Id")]
public string Id { get; set; }
[BsonElement("Title")]
public string Title { get; set; }
[BsonElement("Body")]
public string Body { get; set; }
[BsonElement("Tags")]
public string Tags { get; set; }
public void ShowOnConsole()
{
Console.WriteLine(" _id {0} Id {1} Title {2} Body {3} Tags {4} ", _id, Id, Title, Body, Tags);
}
}
This is my code in Main method
const string connectionString = "mongodb://localhost";
var client = new MongoClient(connectionString);
MongoServer server = client.GetServer();
MongoDatabase database = server.GetDatabase("mydb");
MongoCollection<Element> collection = database.GetCollection<Element>("train");
Console.WriteLine("Zaimportowano {0} rekordow ", collection.Count());
MongoCursor<Element> ids = collection.FindAll();
foreach (Element entity in ids)
{
entity.ShowOnConsole();
}
When i run this code I am able to see some data, but I'v got exception
"Cannot deserialize string from BsonType Int32."
I think that one of property is represented in database as int , but I dont know how to deal with it ? How come one property in one entity is int and the same property in another object is string ?
What I have to do to read all database ?
Yes, String property in C# object has Int32 value in mongo storage, so you have exception during serialization (see code for MongoDB.Bson.Serialization.Serializers.BsonStringSerializer class).
1) You can define your own serializer, which will deserialize Int32 values to string property as well as String ones. Here it is:
public sealed class StringOrInt32Serializer : BsonBaseSerializer
{
public override object Deserialize(BsonReader bsonReader, Type nominalType,
Type actualType, IBsonSerializationOptions options)
{
var bsonType = bsonReader.CurrentBsonType;
switch (bsonType)
{
case BsonType.Null:
bsonReader.ReadNull();
return null;
case BsonType.String:
return bsonReader.ReadString();
case BsonType.Int32:
return bsonReader.ReadInt32().ToString(CultureInfo.InvariantCulture);
default:
var message = string.Format("Cannot deserialize BsonString or BsonInt32 from BsonType {0}.", bsonType);
throw new BsonSerializationException(message);
}
}
public override void Serialize(BsonWriter bsonWriter, Type nominalType,
object value, IBsonSerializationOptions options)
{
if (value != null)
{
bsonWriter.WriteString(value.ToString());
}
else
{
bsonWriter.WriteNull();
}
}
}
Then mark necessary properties (which have different types in MongoDB in your opinion) with this serializer, for example:
[BsonElement("Body")]
[BsonSerializer(typeof(StringOrInt32Serializer))]
public string Body { get; set; }
Also I've found very similar question here: Deserializing field when type is changed using MongoDb csharp driver
2) The second approach - is to 'normalize' your data in storage: convert all integer field values to string. So, you should change field $type from 16 (32-bit integer) to 2 (string). See BSON types. Let's do it for body field:
db.train.find({ 'body' : { $type : 16 } }).forEach(function (element) {
element.body = "" + element.body; // Convert field to string
db.train.save(element);
});
I tried the above example but looks like some class structures have changed. I have a JSON field called BuildingNumber which has number most of the time but in case of Flats or Cottages its left blank. Code is below which is working as expected
public class BsonStringNumericSerializer : SerializerBase<string>
{
public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonType = context.Reader.CurrentBsonType;
switch (bsonType)
{
case BsonType.Null:
context.Reader.ReadNull();
return null;
case BsonType.String:
return context.Reader.ReadString();
case BsonType.Int32:
return context.Reader.ReadInt32().ToString(CultureInfo.InvariantCulture);
default:
var message = string.Format($"Custom Cannot deserialize BsonString or BsonInt32 from BsonType {bsonType}");
throw new BsonSerializationException(message);
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
{
if (value != null)
{
if (int.TryParse(value, out var result))
{
context.Writer.WriteInt32(result);
}
else
{
context.Writer.WriteString(value);
}
}
else
{
context.Writer.WriteNull();
}
}
}
[BsonElement("BUILDING_NUMBER")]
[BsonSerializer(typeof(BsonStringNumericSerializer))]
public string BuildingNumberString { get; set; }
This will work in C# Mongo 2.0+
public class TestingObjectTypeSerializer : IBsonSerializer
{
public Type ValueType { get; } = typeof(string);
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
if (context.Reader.CurrentBsonType == BsonType.Int32) return GetNumberValue(context);
return context.Reader.ReadString();
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
context.Writer.WriteString(value as string);
}
private static object GetNumberValue(BsonDeserializationContext context)
{
var value = context.Reader.ReadInt32();
switch (value)
{
case 1:
return "one";
case 2:
return "two";
case 3:
return "three";
default:
return "BadType";
}
}
}
And you can use it like
public class TestingObject
{
public string FirstName { get; set; }
public string LastName { get; set; }
[BsonSerializer(typeof(TestingObjectTypeSerializer))]
public string TestingObjectType { get; set; }
}
Theoretically you can change strings to objects then you'll be able to deserialize int as well as string. So if you have
Tags as int in DB, this construction will deserialize it and then you can convert it as you wish.
[BsonElement("Tags")]
public object Tags { get; set; }