I have a Dictionary<string, object> as an insertion input for MongoDB.Save().
When I fill this dictionary with primitive types - the serialization process works fine but when I'm trying to insert my dictionary filled with some custom class (for example - Person class) I'm getting the next error:
.NET type 'Person' cannot be mapped to a BsonValue
I noticed that if I insert my custom type converted to BsonDocument the serialization process work great.
How can I define MongoDB to serialize a particular class as BsonDocument?
Update: real code provided
here is my SensorData class:
[DataContract]
public class SensorData
{
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Type { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Desc { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public SensorStatus Status { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Value { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string ValueUnits { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Boolean)]
public bool Event { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Int32)]
public int TTL { get; set; }
}
Here is the methods which saves the data to MongoDB:
public void Save(Dictionary<string, object> RawSensorMessageDictionary)
{
try
{
var policyColl = new MongoClient(ConnString).GetServer().GetDatabase(DB_NAME).GetCollection<BsonDocument>(CollectionNames.RawSensorMessage.ToString());
if (!RawSensorMessageDictionary.Any(p => p.Key == "_id")) //if rawSensorMessageID is empty, mongodb will set it
RawSensorMessageDictionary.Add("_id", ObjectId.GenerateNewId().ToString());
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
policyColl.Save(bsonObj);
}
catch (Exception ex)
{
//log exception
}
}
And here is the code from my UnitTest:
DataAccess.MongoDAL dal = new DataAccess.MongoDAL();
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("1", true);
dic.Add(Message.RESOURCE_TYPE, "aaa");
dic.Add(Message.RESOURCE_NAME, "bbb");
dic.Add(Message.SENSOR_DATA, new SensorData { Desc = "aaa", Event = true, Status = SensorStatus.Active, TTL = 4, Type = "sss", Value = "222" });
dal.SaveRawSensorData(dic );
I want my app to Serialize automatically SensorData object to BsonDocument. If I don't do it manually I'm getting the following exception: .NET type 'SensorData' cannot be mapped to a BsonValue
How should I do it?
Update 2:
OK found the issue, In my "save to db" method I used the next code in order to convert my dictionary BsonDocument:
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
I would expect that creating a new instance of 'BsonDocument' from other object will handle the conversion exactly like object.ToBsonDocument() does but apparently it's not.
Finally I replaced the code above to the following one:
var bsonObj = RawSensorMessageDictionary.ToBsonDocument();
dal.SaveRawSensorData(dic );
now it works.
If the string in the dictionary is the property in your mongodb document, than a non primitive type is a subdocument in mongodb. Please try to mark all properties in the class with [BsonElement] and the Bson Id with [BsonId]. And be sure you follow all rules from here: Serialize Documents with the C# Driver
It would be nice, if you provide some more information
EDIT 1
I would try to remove all properties of SensorData besides Value. If this works, i would than add all other (one by one).
Related
I struggle with C# ASP.NET sending (partially) unstructured data to MongoDb.
How is it possible to add a field with unstructured data (unstructuredInfo) to a otherwise mapped class?
If not - how is BsonDocument or any other clever solution, anyway? I always get JsonExceptions with BsonDocuments.
Get Method works fine. I can't get it working for a Post Method. I get JsonExceptions.
... System.Text.Json.JsonException: The JSON value could not be converted to MongoDB.Bson.BsonDocument. ...
I setup a quick Github Repo with the test project.
https://github.com/Maxoper/MongoDbProblem
public class Movie
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Title { get; set; }
public int Year { get; set; }
public string Summary { get; set; } = null;
public List<string> Actors { get; set; }
[BsonExtraElements]
public BsonDocument UnstructuredInfo { get; set; }
}
public class MoviesService
{
private readonly IMongoCollection<Movie> _movies;
public MoviesService(IOptions<MoviesDatabaseSettings> options)
{
var mongoClient = new MongoClient(options.Value.ConnectionString);
_movies = mongoClient.GetDatabase(options.Value.DatabaseName)
.GetCollection<Movie>(options.Value.MoviesCollectionName);
}
public async Task Create(Movie newMovie) =>
await _movies.InsertOneAsync(newMovie);
app.MapPost("/api/movies", async (MoviesService moviesService, Movie movie) =>
{
await moviesService.Create(movie);
return Results.Ok();
});
The problem is that the JSON cannot be converted to BsonDocument out of the box. Either you create a custom converter or you change the type of the property to IDictionary<string, object>:
[BsonExtraElements]
public IDictionary<string, object> UnstructuredInfo { get; set; }
This way, the JSON serializer does not have to deal with MongoDB specific classes like BsonDocument and you have better chances that everything can be serialized.
This is an old application (.Net 4.5).
I am following the guide here: https://weblog.west-wind.com/posts/2012/aug/30/using-jsonnet-for-dynamic-json-parsing
My goal is to have a way to store a flexible json structure without tying it down to a static structure. For example, in the TriggerJson below, the actual Trigger field is of type string, which is supposed to be json. That json structure could reflect ExpiryTriggerJson, or some other structure which is determined by TriggerType.
I have the following structure:
public class TriggerJson
{
public string TriggerType { get; set; }
public string ConfiguredBy { get; set; }
public string Trigger { get; set; }
}
public class ExpiryTriggerJson
{
public string ActionType { get; set; }
public TriggerRecipient[] Recipients { get; set; }
}
public class TriggerRecipient
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsTag { get; set; }
}
In the following code, I am creating a list of TriggerJson such that each element's Trigger field be a json structure made from ExpiryTriggerJson object:
var tjList = new List<TriggerJson>();
var triggerJson = new TriggerJson();
triggerJson.TriggerType = TriggerJsonHelper.ExpiryTriggerType;
triggerJson.Trigger = JsonConvert.SerializeObject(new ExpiryTriggerJson
{
Recipients = taskRecipients,
ActionType = TriggerJsonHelper.ExpiryTriggerActionType_Task
});
triggerJson.ConfiguredBy = configuredBy;
tjList.Add(triggerJson);
fieldValue.TriggersJson = JsonConvert.SerializeObject(tjList);
This creates the following structure for example where Trigger field reflects a serialized structure which has escaped double quotes due to double serialization:
[{"TriggerType":"ExpiryTrigger","ConfiguredBy":"acd1ac353ac44e078aaef8ce6479a4c6","Trigger":"{\"ActionType\":\"CreateReminderTask\",\"Recipients\":[{\"Id\":\"70050a95-f31b-41b7-9b49-0688fa76dba5\",\"Name\":\"blah blah\",\"IsTag\":false}]}"}]
This creates a problem for me when trying to deserialize this data later on when reading it back:
JArray jsonObj = JArray.Parse(triggersJson);
foreach (dynamic obj in jsonObj)
{
if (obj.TriggerType == ExpiryTriggerType)
{
ExpiryTriggerJson triggerData = obj.Trigger.ToObject<ExpiryTriggerJson>();
The above code tries to parse back ExpiryTriggerJson structure that was first assigned to the field Trigger of TriggerJson element. This throws a runtime deserialization exception when executing the last line trying to convert to ExpiryTriggerJson which I suspect happens due to double deserialization.
My question is how do I accomplish my goal of storing static/structural json data as a string and then parse it back in a nested manner?
This might be what you want.
public dynamic Trigger {get;set;}
Then trigger will be resolved to whatever type it receives at runtime.
I have a group of objects that i want to serialize.
In a specific class, i have two properties, and i know that always one of them it will be null. So, i want serialize this properties with same name and ignoring the one that is null.
The next code is an example. In this case Data is null and Data1 is not, but in the real case the conditions of the problem will determine which one will be null.
public class DataToSerialize
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TData Data { get; set; }
[JsonProperty("Data", NullValueHandling = NullValueHandling.Ignore)]
public TData1 Data1 { get; set; }
public DataToSerialize()
{
Data = null;
Data1 = new TData1();
}
}
When i try to serialize the object is thrown the next exception:
Newtonsoft.Json.JsonSerializationException: 'A member with the name 'Data' already exists on 'DataToSerialize'. Use the JsonPropertyAttribute to specify another name.'
The easiest option might be to decorate both with a [JsonIgnore] attribute, and then have a separate property which supplies the non-null value:
public class DataToSerialize
{
[JsonIgnore]
public TData Data { get; set; }
[JsonIgnore]
public TData1 Data1 { get; set; }
[JsonProperty("data")]
public object SerializableData
{
get { return Data1 == null ? (object)Data : Data1; }
}
}
Or if it's appropriate to your use case, simply use a generic class:
public class DataToSerialize<TData>
{
public TData Data { get; set; }
}
I am retrieving data from office365 api. The response is in JSON format. I want to get data like Id, DisplayName etc. into variables but not getting the right way to do it. Following this link. I'm new to API and JSON. Will Appreciate pointers as well towards best learning links.Sample JSON below for listing sub folders of Inbox folder.
Response JSON data.
{"#odata.context":"https://outlook.office365.com/api/v1.0/$metadata#Me/Folders('Inbox')/ChildFolders","value":
[
{"#odata.id":"https://outlook.office365.com/api/v1.0/Users('sample.user#demosite.com')/Folders('AAMkADBjMGZiZGFlLTE4ZmEtNGRlOS1iMjllLTJmsdfsdfdDSFSDFDFDF=')",
"Id":"AAMkADBjMdfgdfgDFGDFGDFGdfGDFGDFGDFGGDzrACAAB4xqMmAAA=",
"DisplayName":"SampleFolder","ParentFolderId":"AAMkADBjMGZiZGFlLTE4ZmEtNGRlOS1sdsDFSDFSDFSDFSDFSDFDFDFrACAAAAAAEMAAA=","ChildFolderCount":0,"UnreadItemCount":8,"TotalItemCount":94},
{"#odata.id":"https://outlook.office365.com/api/v1.0/Users('sample.user#demosite.com')/Folders('AAMkADBjMGZiZGFlLTE4ZmEasdasdasdASDASDASDASDSADDASDASDAB4xqMnAAA=')",
"Id":"AAMkADBjMGZiZGFlLTE4ZmEtNGRlOS1iMjllLTJmOGZkNGRhZmIzNQAuAasdASDASDASDASEDASDASDxSEHjzrACAAB4xqMnAAA=",
"DisplayName":"AnotherSampleFolder","ParentFolderId":"AAMkADBjMGZiZGFlLTE4ZmEtNGRlOS1sdsDFSDFSDFSDFSDFSDFDFDFrACAAAAAAEMAAA=","ChildFolderCount":0,"UnreadItemCount":21,"TotalItemCount":75}
]
}
The C# code using to parse JSON and find the required data.
HttpResponseMessage response = httpClient.SendAsync(request).Result;
if (!response.IsSuccessStatusCode)
throw new WebException(response.StatusCode.ToString() + ": " + response.ReasonPhrase);
string content = response.Content.ReadAsStringAsync().Result;
JObject jResult = JObject.Parse(content);
if (jResult["odata.error"] != null)
throw new Exception((string)jResult["odata.error"]["message"]["value"]);
//Attempt one - using dynamic [NOT WORKING - getting NULL values in the variables]
dynamic results = JsonConvert.DeserializeObject<dynamic>(content);
var folderName = results.Id;
var folderId = results.Name;
//Attempt two - [Not working - Throwing exception -
//Object reference not set to an instance of an object.]
var folderID = (string)jResult["odata.context"]["odata.id"][0]["Id"];
First create a class for your json object
public class RootObject
{
[JsonProperty(PropertyName = "#odata.context")]
public string context { get; set; }
public List<Value> value { get; set; }
}
public class Value
{
[JsonProperty(PropertyName = "#odata.id")]
public string dataId { get; set; }
public string Id { get; set; }
public string DisplayName { get; set; }
public string ParentFolderId { get; set; }
public int ChildFolderCount { get; set; }
public int UnreadItemCount { get; set; }
public int TotalItemCount { get; set; }
}
Then Json Convert the Json string to your RootObject if your are using Newtonsoft Json then Deserilaze by using
RootObject shortiee = JsonConvert.DeserializeObject<RootObject>("Your Json String");
private List<string> GetDisplayNames(JObject content)
{
var obj = Json.Parse(content);
var values = obj["value"].ToList();
var displayNames = new List<string>();
foreach (var value in values)
{
displayNames .Add(system["DisplayName"].ToString());
}
return displayNames;
}
This would return the names, for example, and you could do this for each value you need to retrieve. However, this does not require you to serialize/deserialize the json object before using it. It works, but is most likely not best practice.
if (jResult["odata.error"] != null)
throw new Exception((string)jResult["odata.error"]["message"]["value"]);
//Attempt one - using dynamic [NOT WORKING - getting NULL values in the variables]
dynamic results = JsonConvert.DeserializeObject<dynamic>(content);
Side note: There is no key called "odata.error" in your JSON data. So you're effectively calling something which will return null.
One of the ways to deserialise JSON is to create model classes for the objects you want to process and deserialise into them directly, eg. JsonConvert.DeserializeObject<Folder>(content). As you are talking to an Office365 API, you find documentation and examples here on how they are defined.
Taken your folder response as an example, your model for a single Folder could look like this:
public class Folder
{
[JsonProperty(PropertyName = "#odata.id")]
public string OdataId { get; set; }
public string Id { get; set; }
public string DisplayName { get; set; }
public string ParentFolderId { get; set; }
public int ChildFolderCount { get; set; }
public int UnreadItemCount { get; set; }
public int TotalItemCount { get; set; }
}
Note1: in your example, you get a response with list of folders, so have to adjust this accordingly.
Note2: you can use JsonProperty to define a mapping between a JSON property/key and a C# property as shwon for #odata.id.
However, you can also use the Outlook Client Library which would make it mostly unnecessary to deal with JSON data directly (which seems preferable, unless you have a very specific reason to deal with JSON directly).
Im trying to serialize my dictionary that looks like that:
private Dictionary<MetaDataKey, User> _dictionary;
where MetaDataKey and Users classes looks like that:
internal class User
{
public string UserName { get; set; }
public string UserPassword { get; set; }
public List<Account> Accounts { get; set; }
}
internal class Account
{
public string Subject { get; set; }
public string AccName { get; set; }
public string AccPass { get; set; }
public List<string> Notes { get; set; }
}
internal class MetaDataKey
{
public string Name { get; set; }
public string Password { get; set; }
}
I am trying to save\load the dictionary to\from a json file like this:
private void DictionaryInit()
{
//gets the dictionary file if exists, create an empty one if not.
string path = Directory.GetCurrentDirectory() + "\\dic.json";
if (!File.Exists(path))
{
_dictionary = new Dictionary<MetaDataKey, User>();
return;
}
using (StreamReader r = new StreamReader(path))
{
string json = r.ReadToEnd();
_dictionary = JsonConvert.DeserializeObject<Dictionary<MetaDataKey, User>>(json);
}
}
public void DictionarySave()
{
//save the dictionary into dic.json file
string path = Directory.GetCurrentDirectory() + "\\dic.json";
string json = JsonConvert.SerializeObject(_dictionary);
File.WriteAllText(path, json);
}
when I am loading a new record to the dictionary and trying to save it I get:
{"WpfApplication2.MetaDataKey":{"UserName":"Enter Name","UserPassword":"Enter Password","Accounts":null}}
instead of:
{"WpfApplication2.MetaDataKey":{"Name":"Enter Name","Password":"Enter Password"},"WpfApplication2.User":{"UserName":"Enter Name","UserPassword":"Enter Password","Accounts":null}}
as you can tell, I am getting the fields of Users in MetaDataKey class.
even after I fix it manualy I am still getting exception:
An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code
when I am trying to load a non-empty file.
In conclusion, 2 problems:
1. bad json saving.
2. bad json loading
From the documentation of Json.Net:
When serializing a dictionary, the keys of the dictionary are converted to strings and used as the JSON object property names. The string written for a key can be customized by either overriding ToString() for the key type or by implementing a TypeConverter. A TypeConverter will also support converting a custom string back again when deserializing a dictionary.
You have two options:
like the documentation suggests: create a TypeConverter for your MetaDataKey and link it with attribute ([TypeConverter(typeof(MetaDataKeyConverter))]) - This is not trivial as you will have to convert the MetaDataKey to json string yourself, and also deserialize from string.
Create a JsonConverter for dictionary and use it in your JsonConvert.SerializeObject and JsonConvert.DeserializeObject methods.
The simplest thing you can do is to convert the dictinary to a List<KeyValuePair<MetaData,User>> this is easy as _dictionary.ToList()
So for serializing:
string json = JsonConvert.SerializeObject(_dictionary.ToList());
And for deserialize:
_dictionary =
JsonConvert.DeserializeObject<List<KeyValuePair<MetaDataKey, User>>>(json)
.ToDictionary(kv => kv.Key, kv => kv.Value);
For most cases I would choose option 3
Try to use JsonProperty attribute like the following:
internal class User
{
[JsonProperty("UserName ")]
public string UserName { get; set; }
[JsonProperty("UserPassword")]
public string UserPassword { get; set; }
[JsonProperty("Accounts ")]
public List<Account> Accounts { get; set; }
}