I am using MongoDb with C# in a WebApi app. My data does not lend well to mapped data types due to the dynamic nature of the document stored. For example, here is an example of some data. Note the Data section with values that can either be arrays or a single string (Technician):
{
"_id" : "5a59129d16d5c42f7444b83d",
"CreatedDate" : "2018-01-09T20:30:19.455Z",
"Data" : {
"AlcoholTest" : [
{
"Technician" : [
"STT",
"BAT"
],
"TestReason" : "not well"
}
]
}
}
When I attempt to return the data like above, I get this instead:
{
"_id": {
"$oid": "5a59129d16d5c42f7444b83d"
},
"CreatedDate": {
"$date": 1515529819455
},
"Data": {
"AlcoholTest": [
{
"Technician": [
"STT",
"BAT"
],
"TestReason": "drunk"
}
]
}
}
Here is the code I'm using:
public object FindById(string id)
{
var filter = new BsonDocument { { "_id", ObjectId.Parse(id) } };
var result = _collection2.Find(filter);
var note = result.Any() ? result.First() : null;
var json = note.ToJson(new JsonWriterSettings{OutputMode = JsonOutputMode.Strict});
return JObject.Parse(json);
}
I can't just return the note object since Newtonsoft does not know how to convert those $data and $oid into valid types and returns a parsing error.
When I attempted to use MongoDb mapping classes in .NET, this is what my class looked like (I'm not including the BsonClassMap.RegisterClassMap stuff for simplicity):
public class Note
{
public string Id { get; set; }
public DateTime? CreatedDate { get; set; }
public IDictionary<string, IList<IDictionary<string, object>>> Data { get; set; }
}
When I tried this code, the Newtonsoft did not know how to handle the case where the object in IList<IDictionary<string, object>> could be an array or string and saved some rather nasty JArray and JObject data instead.
So here are my questions:
Is there a way to make the above C# code return identical JSON as what is represented in the Mongo database (or what the UI is sending and expecting to get back)?
Or is there a way to map my Data document using .NET types that would allow me to use MongoDb mapping classes that accepts either string values, arrays, or both?
I ended up using a different approach for save vs get. For saving, I converted a generic object to BsonDocument, and saved it as it. This resulted in the data format I was expecting (first data format my above question).
I used a strongly-typed collection to get data from the MongoDB so I could avoid the second data format from my question above. To get the data returned in the correct format, I changed this property from
public IDictionary<string, IList<IDictionary<string, IList<string>>>> Data { get; set; }
to
public IDictionary<string, object> Data { get; set; }
This gave me the correctly formatted data I needed. Having two MongoDb collections for getting vs saving was not ideal, but it eliminated my original hack to parse the dictionary BsonDocument into my strongly-typed POCO.
Related
I have slightly annoying use case.
So, I am calling an API . The APi returns some json, which contains a json object with x amount of fields:
{
"status": "ok",
"result": {
"firstprovider": [ .... ],
"secondprovider": [ ...],
"thirdprovider": [ ... ]
}
}
In this example, only three provider are returned, but I could get more or less than that, and their names may vary. It's quite important that I save those names.
The "providers" inside of "result" are of the same type, so I can easily deserialize those to a certain model.
Now, I would normally expext "result" to be a json array of elements, so I could easily deserialize "result" into a List<LiveShopperEventModel>.
Currently, I made a hacky solution, creating a Dictionary<string,Provider> where the key is the name of the provider, and then I later use selectmany to flatten it into a list.
But, I was wondering if there exists some way in c#, that would allow me to convert the "result", into an array, which would make deserialization a lot simpler for me.
So, does anyone know of a way, or a resource that in c# can help in changing the types of json elements, and in this case, making a json object into a json array, with the fields becoming elements in the list?
Reproducing concept with minimal example
So, let's say my json looks like this:
{
"status": "ok",
"result": {
"firstprovider": [ {"name":"John"}, {"car":"BMW"}, {"surname":"Johnson"} ],
"secondprovider": [ {"name":"Zoe"}, {"car":"Ford"}, {"surname":"johnsøn"}],
"thirdprovider": [{"name":"Elliot"}, {"car":"Volkswagen"}, {"surname":"Jackson"} ]
}
}
Then I can deserialize it as in the following code snippet:
string json = "{\r\n\"status\": \"ok\", \r\n\"result\": { \r\n \"firstprovider\": [ {\"name\":\"John\"}, {\"car\":\"BMW\"}, {\"surname\":\"Johnson\", \"age\":30, \"car\":\"fast\"} ],\r\n \"secondprovider\": [ {\"name\":\"Zoe\"}, {\"car\":\"Ford\"}, {\"surname\":\"johnsøn\", \"age\":31, \"car\":null}], \r\n \"thirdprovider\": [{\"name\":\"Elliot\"}, {\"car\":\"Volkswagen\"}, {\"surname\":\"Jackson\", \"age\":32, \"car\":null} ] \r\n }\r\n }\r\n";
// deserializing to a dictionary
var resultDict = JsonConvert.DeserializeObject<ResultModel>(json);
// and now flattening the structure, so that it is a list of "ProviderModel"
// this is the part that feels hacky to me
var providerModelList = resultDict.result.SelectMany(listOfEvents => {
listOfEvents.Value.Select(Provider =>
{
Provider.provider = listOfEvents.Key;
return Provider;
}).ToList();
return listOfEvents.Value;
}).ToList();
public class ResultModel
{
[JsonProperty("result")]
public Dictionary<string, List<ProviderModel>> result { get; set; }
}
public class ProviderModel
{
public string provider { get; set; }
public string name { get; set; }
public string surname { get; set; }
}
The `selectmany" part feels hacky to me, since, i'm combining linq queries in a way that feels overly complicated
Instead, I think it would be much nicer, is the result class could just look like:
public class ResultModel
{
[JsonProperty("result")]
public List<ProviderModel> result { get; set; }
}
you can try something like this
List<ProviderModel> providerModelList = ((JObject)JObject.Parse(json)["result"])
.Properties()
.Select(x => GetValues(x))
.ToList();
public ProviderModel GetValues(JProperty jProp)
{
var providerModel = new JObject(((JArray)jProp.Value)
.Select(jo => ((JObject)jo).Properties().First()))
.ToObject<ProviderModel>();
providerModel.provider = jProp.Name;
return providerModel;
}
C#
Let's say that I want to make a winform page that receives a certain amount of random features read from the contents of a json file. (while using the 'Newtonsoft.Json' library)
Code that Deserializes the json file
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(File.ReadAllText(#"..\..\Json\features.json"));
These classes are set to represent the contents and structure of the json file within the same c# file, but outside of the main class of course.
class Root
{
public List<Category> category { get; set; }
}
class Category
{
public string name { get; set; }
public List<Feature> feature { get; set; }
}
class Feature
{
public string name { get; set; }
public string frequency { get; set; }
}
I have followed the practices to make this happen here:
https://www.newtonsoft.com/json/help/html/DeserializeWithJsonSerializerFromFile.htm
Can not deserialize JSON with array in array
https://json2csharp.com/
It works, however there are two main problems I can see with this:
It can end up visually cluttering the .cs file with lots of classes that you'll never need to look or edit, especially when it is interpreting multiple json files
It isn't dynamic, and it will only work for one json file that you have to make compatible by creating these classes for every subclass that exists within the json file.
My question: Is there a way to deserialize the json file without having to resort to creating multiple classes for every sub-category of data in the json string?
I believe you don't need the classes, because you don't need the whole JSON string, is that correct?
If so, instead of deserializing the whole json file, you could partially deserialize only the parts which you are interested in.
Have a look at this example from the Newtonsoft.Json documentation, where we have a long json string representing a response from a Google search, but are only interested in the responseData/results part of it, and only in some fields of that result object:
Object to partially desierialize:
public class SearchResult
{
public string Title { get; set; }
public string Content { get; set; }
public string Url { get; set; }
}
Deserializing Partial JSON Fragment Example:
string googleSearchText = #"{
'responseData': {
'results': [
{
'GsearchResultClass': 'GwebSearch',
'unescapedUrl': 'http://en.wikipedia.org/wiki/Paris_Hilton',
'url': 'http://en.wikipedia.org/wiki/Paris_Hilton',
'visibleUrl': 'en.wikipedia.org',
'cacheUrl': 'http://www.google.com/search?q=cache:TwrPfhd22hYJ:en.wikipedia.org',
'title': '<b>Paris Hilton</b> - Wikipedia, the free encyclopedia',
'titleNoFormatting': 'Paris Hilton - Wikipedia, the free encyclopedia',
'content': '[1] In 2006, she released her debut album...'
},
{
'GsearchResultClass': 'GwebSearch',
'unescapedUrl': 'http://www.imdb.com/name/nm0385296/',
'url': 'http://www.imdb.com/name/nm0385296/',
'visibleUrl': 'www.imdb.com',
'cacheUrl': 'http://www.google.com/search?q=cache:1i34KkqnsooJ:www.imdb.com',
'title': '<b>Paris Hilton</b>',
'titleNoFormatting': 'Paris Hilton',
'content': 'Self: Zoolander. Socialite <b>Paris Hilton</b>...'
}
],
'cursor': {
'pages': [
{
'start': '0',
'label': 1
},
{
'start': '4',
'label': 2
},
{
'start': '8',
'label': 3
},
{
'start': '12',
'label': 4
}
],
'estimatedResultCount': '59600000',
'currentPageIndex': 0,
'moreResultsUrl': 'http://www.google.com/search?oe=utf8&ie=utf8...'
}
},
'responseDetails': null,
'responseStatus': 200
}";
// Parse JSON into a JObject, which we can easily traverse
JObject googleSearch = JObject.Parse(googleSearchText);
// get JSON result objects into a list
IList<JToken> results = googleSearch["responseData"]["results"].Children().ToList();
// serialize JSON results into .NET objects
IList<SearchResult> searchResults = new List<SearchResult>();
foreach (JToken result in results)
{
// JToken.ToObject is a helper method that uses JsonSerializer internally
SearchResult searchResult = result.ToObject<SearchResult>();
searchResults.Add(searchResult);
}
// Title = <b>Paris Hilton</b> - Wikipedia, the free encyclopedia
// Content = [1] In 2006, she released her debut album...
// Url = http://en.wikipedia.org/wiki/Paris_Hilton
// Title = <b>Paris Hilton</b>
// Content = Self: Zoolander. Socialite <b>Paris Hilton</b>...
// Url = http://www.imdb.com/name/nm0385296/
This way, we only need to create a class for SearchResult and for nothing else, which sounds like what you want to have. While traversing the JSON object with code like googleSearch["responseData"]["results"] you can check whether the result is null and act accordingly, which means you can have optional fields in your JSON file, which are not present in other files, without your code breaking.
Does this help you solve your issues?
I believe that best practice is using classes.
Here are my arguments for using classes in your case:
You can read json bit by bit, use JObject.Parse or even dynamic (ugh) but it think your code should depend on a class (a dto) not on a json string. This input structure happens to be stored in json, but may not be. Most of your unit tests should take in an object, not a string.
I find the argument of deserialising to classes not being dynamic weak because you need to write the code that will handle the added elements. In other words if you add a new feature to json it won't just work, you need to write the code to support it and each time you change the json structure you need to update your code anyway.
i've this json and i need an help to iterate over Elenco.
This is my json
var json = #"
{
{
"Id": 0,
"Point": "123",
"Elenco": [
{
//useless fields
//...
{
"values_": {
"flat": [
1.0, 2.0
]
},
"idPoint": "123",
},
},
{
//useless fields
//...
{
"values_": {
"flat": [
3.0, 6.0
]
},
"idPoint": "1234",
},
},
{
//Other fields here like values, flat and idPoint for x times
},
]
}
}";
I can easily get Id and Point with
JObject object = JObject.Parse(json);
string point = (string)jsonObject["Point"];
but i need to take flat and idPoint and i don't know how do that.
I also need to iterate over Elenco and I have to find which point equals idPunto.
How can i do? Thanks
The best way to achieve what you are trying to do is go via the route of creating the models that represent your json structure and then use a simple json serializer/deserializer library like JSON.Net to deserialize it and access easily its properties.
From what i see, your json model looks something like this:
public class JsonModel
{
public int Id {get; set;}
public string Point {get; set;}
public IEnumerable<JsonModel2> Elenco {get;set;}
}
public class JsonModel2
{
//useless fields
public JsonModel3 SomeProperty{get; set;}
}
public class JsonModel3
{
//useless fields
// i am assuming here that it is a dictionary as it wasnt clear from the json
public Dictionary<string, JsonModel4> DictProperty{get; set;}
}
public class JsonModel4
{
//useless fields
public IEnumerable<float> Flat{get; set;}
}
i am pretty sure i made a mistake here or there in the way i defined the models above, as i feel that the Json itself is not totally correct or is missing a few key elements. Nonetheless, once you define the models correctly with all the elements in your json as a part of your model, you can simply then use
JsonConvert.DeserializeObject<JsonModel>(json);
this will nicely parse and populate the model values correctly, thereby allowing you to iterate over the model properties like a regular c# object.
In RavenDB 4.2, I want to create an Index/Map based on a dynamic object. Better put, on dynamic properties which are not known at compile-time.
Here is an example of the raw JSON I'm ingesting:
{
"id": "A",
"detections":
[
{
"steps": [
{
"object": {
"id": "A1",
"target": {
"domain_name": "foobar.com"
}
},
"object": {
"id": "A2",
"target": {
"ipv4": "127.0.0.1"
}
}
}
]
}
]
}
The above sample is ingested from a 3rd party and stored in a RavenDB collection. Roughly translated, the following model has the challenge:
public class Step
{
public string Id { get; set; }
public DateTime When {get; set;}
public dynamic Object { get; set; } // aware that it's not handy naming
}
The pickle in this is that the object.target.X property name is dynamic. They cannot be strong-typed and can be a lot of things, like: domain_name, ipv4, ipv6, dns, shoe_size, hair_colour etc. This is why the entire steps.object is ingested and stored as either System.Object or dynamic.
My objective is to basically do a SelectMany() on each object.target and extract the property name (key) and value. This would make my RavenDB Index something like this:
public class StepsIndex : AbstractIndexCreationTask<Models.Step, StepsIndex.Result>
{
public class Result
{
public DateTime When { get; set; }
public string TargetKey { get; set; }
public string TargetValue { get; set; }
// ... removed other properties for brevity
}
public StepsIndex()
{
Map = steps =>
from block in blocks
from detection in blocks.Detections
from step in detection.Steps
select new Result
{
// extract property name (key), like 'domain_name'
TargetKey = step.Object.target.GetType().GetProperties()[0].Name,
// extract property value, like 'foobar.com'
TargetValue = step.Object.target.GetType().GetProperty(s.Object.target.GetType().GetProperties()[0].Name).GetValue(s.Object.target, null)
};
}
}
Unfortunately this doesn't work due to step.Object being dynamic and resulting in the following error during compile-time:
Error [CS1963] An expression tree may not contain a dynamic operation
Second option I've tried is to cast it to JSON in the expression, which also fails because Raven's projection is not aware of Newtonsoft.Json during runtime:
// Error CS0103: The name 'JObject' does not exist in the current context
// Error CS0103: The name 'JsonConvert' does not exist in the current context
TargetKey = JObject.Parse(JsonConvert.SerializeObject(ass.Object))["target"][0].Value<string>(),
A third option I thought of was perhaps changing the dynamic Object to System.Object Object, but haven't found a neat way to extract the property key/values without knowning the property.
The question: how can I extract these dynamic property keys and values and Map them to a RavenDB index?
RavenDB allows to index dynamic fields:
See:
https://ravendb.net/docs/article-page/4.2/Csharp/indexes/using-dynamic-fields
https://github.com/ravendb/book/blob/v4.0/Ch10/Ch10.md#dynamic-data
I have a Json service I cannot alter as it is not mine.
Their Json is a formatted in a way that parsing it is difficult. It looks something like this.
"people": {
"Joe Bob": {
"name": "Joe Bob",
"id": "12345"
},
"Bob Smith": {
"name": "Bob Smith",
"id": "54321"
}
},
I would really prefer this was laid out like a JSon array, however it presently is not.
I am wondering the best approach here. Should I alter the Json to look like an array before I parse it or load up the ExtensionData and parse it from that?
There are other items in the feed that I do not have issue with. Just stuck with this one section.
Thanks
You can use json.net to deserialize the data (the json you pasted, and doing only one parsing, without modifying anything).
using dynamic foo = JsonConvert.DeserializeObject<dynamic>(data)
than, you can iterate the list using foo.people, accessing the Name and Value.
you can create a class (if you know what the schema is, and to deserialize the data into a list of the given class such as:
public class People
{
[JsonProperty(PropertyName="people")]
public IDictionary<string, Person> Persons { get; set; }
}
public class Person
{
[JsonProperty(PropertyName="name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
}
and than call:
var obj = JsonConvert.DeserializeObject<People>(data);
foreach (var item in obj.Persons.Values)
{
//item is instance of Person
}
Another good and possible option will be:
How can I navigate any JSON tree in c#?