Adding serialization information for MongoDB custom serializer - c#

I have a class defined as follows:
class Person
{
public String Id { get; set; }
public String Name { get; set; }
public Person Mother { get; set; }
}
I've implemented a custom serializer for the Mother property to serialize the Id only. The final BSON would look something like this:
[{
"_id": "54df1095fa0bd7122cb2c550",
"name": "John",
"mother": { "_id": "54df1095fa0bd7122cb2c551" }
}]
If I try to execute a query to find a person with a given mother as follows:
var results = await collection.Find<Person> (p => p.Mother.Id == "...").ToListAsync ();
The driver complains with the following:
{"Unable to determine the serialization information for the expression: p.Mother.Id."}
Is there a way to add serialization info to the driver so it knows to call my custom serializer to deserialize Person for this type of query?

Yes, there are two interfaces you may implement to provide serialization information. IBsonDocumentSerializer and IBsonArraySerializer. In this case, you'll want to implement IBsonDocumentSerializer on your customer serializer and handle the GetMemberSerializationInfo call for the memberName Id.

Related

How to ignore/unwrap object name when deserializing from JSON?

I am trying to deserialize JSON that i receive from a webservice. The JSON looks like that:
{ "data":
[
{"name": "john", "company": "microsoft"},
{"name": "karl", "company":"google"}
]
}
My model which i want to deserialize into:
public class employee {
public string name {get; set;}
public string company {get; set;}
}
The problem is, that i cannot deserialize using System.Text.Json because of that object name "data". How can i make the deserializer to unwrap / ignore the data tag and just start from whatever is inside that tag?
Just create a wrapper object and use it for deserialization:
public class Root {
public List<employee> data {get; set;}
}
var employees = JsonSerializer.Deserialize<Root>(jsonString).data;
In case there a lot of different types contains this pattern you can make Root generic:
public class Root<T> {
public List<T> data {get; set;}
}
var employees = JsonSerializer.Deserialize<Root<employee>>(jsonString).data;
Note that data contains a collection of employees not a single record.
Also note that you can use recommended Pascal casing for property names, deserializer should be able to pick it up (of it it does not - you can help it by providing JsonSerializerOptions with correct PropertyNamingPolicy set).

What am I doing wrong when trying to deserialize json into a c# list?

I have a Product class:
class Product
{
public string Name;
}
A Product List class:
class ProductDataFile
{
public List<Product>? products;
}
And a class for loading json into these classes:
public void LoadProducts()
{
string jsonString = File.ReadAllText(FileLoc);
ProductDataFile? productDataFile= JsonSerializer.Deserialize<ProductDataFile>(jsonString);
var ProductName = productDataFile.products.First().Name;
}
This throws a "System.ArgumentNullException: Value cannot be null. (Parameter 'source')". I used the debugger and products is null, so that seems to be the problem.
My Json looks something like this:
{
"listOfProducts": "List of products",
"products": [
{
"Name": "product one"
}
]
}
```
The library Newtonsoft.json method of JsonSerializer.Deserialize need to use properties instead of fields, otherwise you might not Deserialize anything from your JSON data.
class Product
{
public string Name {get;set;}
}
class ProductDataFile
{
public List<Product>? products {get;set;}
}
I would suggest you use json2csharp, it can easy to get the model by JSON and make sure it will be work.

JSON Root Node - Different From Class Name

I need to get my JSON output looking like this:
{
"User": {
"Id" : "1",
"FirstName" : "John",
"LastName" : "Doe",
... etc
My first issue is that the class name being serialized here is called Person not User, and I am not able to change that. But the JSON needs User.
Secondly, my Web API method is not returning a root node here at all, what exactly am I doing wrong?
My JSON looks like this:
{"Id":1,"BossId":null,"Title":"CEO","GivenName":"Doe", ... etc
This is so badly formatted that even my Chrome extension to make JSON pretty doesn't recognize this stuff.
Here's my Web API controller to get a user by ID, which is resulting in the above:
[Route("{id:int}")]
public HttpResponseMessage GetPerson(int id) {
Person person = repository.Get(id);
if (person == null) {
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, person);
}
I am also going to have to map class properties to different names here, which is a separate issue.
For example, the class has GivenName and FamilyName but the JSON needs FirstName and LastName. Is there an attribute I can apply to the property for this?
Any help would be appreciated.
ASP.NET WebApi uses JSON.NET for serialization to json. So you can change name in this way:
class Person
{
[JsonProperty(PropertyName = "LastName")]
public string FamilyName { get; set; }
...
}
EDIT
For add root element look at this answer. I didn't try this but looks nice.
Assuming you are using the Newtonsoft Json.Net, the most popular .Net Json serializer, following modifications are required:
Wrap the Person object inside a wrapper and assign a JsonProperty to it:
[JsonObject]
public class Wrapper
{
[JsonProperty("User")]
public Person Person {get; set;}
}
Now use the same JsonProperty inside the Person class too:
[JsonObject]
public class Person
{
[JsonProperty("FirstName")]
public string GivenName {get; set;}
[JsonProperty("LastName")]
public string FamilyName {get; set;}
... More Properties
}
Now while filling the response.following need to be done:
Wrapper w = new Wrapper();
w.Person = <assign Value>
return Request.CreateResponse(HttpStatusCode.OK, w);
One last thing Json unlike XML doesn't have a concept of a root node, it's nameless, that's why wrapper doesn't come anywhere and it would start from first object marked as User in this case, Json is a like an anonymous type in C#, internally a Key Value pair, since Keys are always string

JSON.NET Abstract / Derived Class Deserialization with WebAPI 2

I'm implementing a Web API 2 service that uses JSON.NET for serialization.
When I try to PUT ( deseralize ) updated json data, the abstract class is not present meaning it didn't know what to do with it so it did nothing. I also tried making the class NOT abstract and just inheriting from it and then each PUT deseralized to the base class rather than the derrived class missing the properties of the derrived class.
Example:
public class People
{
// other attributes removed for demonstration simplicity
public List<Person> People { get;set; }
}
public abstract class Person
{
public string Id {get;set;}
public string Name {get;set;}
}
public class Employee : Person
{
public string Badge {get;set;}
}
public class Customer : Person
{
public string VendorCategory {get;set;}
}
with my web api configured to do typename handling:
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling =
TypeNameHandling.Objects;
}
then I PUT the JSON like:
{
people: [{
name: "Larry",
id: "123",
badge: "12345",
$type: "API.Models.Employee, API"
}]
}
to the web api method:
public HttpResponseMessage Put(string id, [FromBody]People value)
{
people.Update(value); // MongoDB Repository method ( not important here )
return Request.CreateResponse(HttpStatusCode.OK);
}
but the output when inspecting value is always:
People == { People: [] }
or if non-abstract:
People == { People: [{ Name: "Larry", Id: "123" }] }
missing the inherrited property. Anyone ran into this problem and come up with anything?
The $type function has to be the first attribute in the object.
In the above example I did:
{
people: [{
name: "Larry",
id: "123",
badge: "12345",
$type: "API.Models.Employee, API"
}]
}
after moving $type to the top like:
{
people: [{
$type: "API.Models.Employee, API",
name: "Larry",
id: "123",
badge: "12345"
}]
}
the serializer was able to deseralize the object to the correct cast. Gotta love that!
I have tried your scenario now and it works fine. But I did notice that you are missing a , (comma) after the id property in your json input.
I figured this out by using the following ModelState validity check in my action which then showed the error in my request payload. This could be useful to you too:
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
}
I know this post is old now and the answer has been marked, but I thought my solution might be helpful....
Try adding the JsonProperty attribute to the properties on your abstract class.
using JTC.Framework.Json;
...
public class People
{
// other attributes removed for demonstration simplicity
public List<Person> People { get;set; }
}
public abstract class Person
{
[JsonProperty()]
public string Id {get;set;}
[JsonProperty()]
public string Name {get;set;}
}
public class Employee : Person
{
public string Badge {get;set;}
}
public class Customer : Person
{
public string VendorCategory {get;set;}
}
JsonSubTypes library allows specifying which subclass of the given class should be used to deserialize into via attributes just like Jackson library in Java does. To be more specific, you can:
Choose a field and specify its value for each subclass, or
Specify fields present only in certain subclass.
I had a very similar issue. What worked for me was to add a default constructor that initializes the objects in your class. Make sure you initialize each object.
In your case, you need to add the constructor to the People class.
public class People
{
public People()
{
People = new List<Person>();
}
public List<Person> People { get;set; }
}
Also, this seems to be an all-or-nothing shot. If you do not initialize any contained objects, none of them will contain values.

Deserializing JSON using C#

Finding some difficulty in sourcing information in trying to deserialize JSON in C#.
I have results from Google custom search returned in JSON format. I just want to check through my steps and establish the order in trying to deserialize it. Is this right?
I need to create classes to match
the JSON format. Kind of like a
creating schema file.
Use the JavaScriptSerializer() class and
deserialize method to extract the
relevant bits.
One of the issues I think I am going to run into is that I don't require all of the data returned but only the html links. How can I achieve that?
UPDATE
I have updated my question with the following JSON snippet and C# code. I want to output the string 'links' to console but it doesn't seem to be working. I think I am defining my classes wrongly?
JSON from Google Custom Search
handleResponse({
"kind": "customsearch#search",
"url": {
"type": "application/json",
"template": "https://www.googleapis.com/customsearch/v1?q\u003d{searchTerms}&num\u003d{count?}&start\u003d{startIndex?}&hr\u003d{language?}&safe\u003d{safe?}&cx\u003d{cx?}&cref\u003d{cref?}&sort\u003d{sort?}&alt\u003djson"
},
"queries": {
"nextPage": [
{
"title": "Google Custom Search - lectures",
"totalResults": 9590000,
"searchTerms": "lectures",
"count": 1,
"startIndex": 2,
"inputEncoding": "utf8",
"outputEncoding": "utf8",
"cx": "017576662512468239146:omuauf_lfve"
}
],
"request": [
{
"title": "Google Custom Search - lectures",
"totalResults": 9590000,
"searchTerms": "lectures",
"count": 1,
"startIndex": 1,
"inputEncoding": "utf8",
"outputEncoding": "utf8",
"cx": "017576662512468239146:omuauf_lfve"
}
]
},
"context": {
"title": "Curriculum",
"facets": [
[
{
"label": "lectures",
"anchor": "Lectures"
}
],
[
{
"label": "assignments",
"anchor": "Assignments"
}
],
[
{
"label": "reference",
"anchor": "Reference"
}
]
]
},
"items": [
{
"kind": "customsearch#result",
"title": "EE364a: Lecture Videos",
"htmlTitle": "EE364a: \u003cb\u003eLecture\u003c/b\u003e Videos",
"link": "http://www.stanford.edu/class/ee364a/videos.html",
"displayLink": "www.stanford.edu",
"snippet": "Apr 7, 2010 ... Course materials. Lecture slides · Lecture videos (2008) · Review sessions. Assignments. Homework · Reading. Exams. Final exam ...",
"htmlSnippet": "Apr 7, 2010 \u003cb\u003e...\u003c/b\u003e Course materials. \u003cb\u003eLecture\u003c/b\u003e slides · \u003cb\u003eLecture\u003c/b\u003e videos (2008) · Review sessions. \u003cbr\u003e Assignments. Homework · Reading. Exams. Final exam \u003cb\u003e...\u003c/b\u003e",
"cacheid": "TxVqFzFZLOsJ"
}
]
}
);
C# Snippet
public class GoogleSearchResults
{
public string link { get; set; }
}
public class Program
{
static void Main(string[] args)
{
//input search term
Console.WriteLine("What is your search query?:");
string searchTerm = Console.ReadLine();
//concantenate the strings using + symbol to make it URL friendly for google
string searchTermFormat = searchTerm.Replace(" ", "+");
//create a new instance of Webclient and use DownloadString method from the Webclient class to extract download html
WebClient client = new WebClient();
string Json = client.DownloadString("https://www.googleapis.com/customsearch/v1?key=My Key&cx=My CX&q=" + searchTermFormat);
//create a new instance of JavaScriptSerializer and deserialise the desired content
JavaScriptSerializer js = new JavaScriptSerializer();
GoogleSearchResults results = js.Deserialize<GoogleSearchResults>(Json);
Console.WriteLine(results);
//Console.WriteLine(htmlDoc);
Console.ReadLine();
}
}
Thanks
I use your #2 approach: deserialize with the JavaScriptSerializer.
This is what I do to deserialize a response from Facebook:
// get the id for the uploaded photo
var jss = new JavaScriptSerializer();
var resource = jss.Deserialize<Facebook.Data.Resource>(responseText);
....where Facebook.Data.Resource is defined like this:
namespace Facebook.Data
{
public class Resource
{
public string id { get; set; }
}
}
The responseText that I am deserializing from looks like this:
{"id":"10150111918987952",
"from":{"name":"Someone",
"id":"782272221"},
"name":"uploaded from Cropper. (at 12\/15\/2010 7:06:41 AM)",
"picture":"http:\/\/photos-f.ak.fbcdn.net\/hphotos-ak-snc4\/hs817.snc4\/69790_101501113333332_782377951_7551951_8193638_s.jpg",
...
But since I have only one property defined in the Resource class, I only deserialize that. Define the fields in your class that you want to deserialize.
It works to use inheritance, of course. You can define your data classes like this:
namespace Facebook.Data
{
public class Resource
{
public string id { get; set; }
}
public class Person : Resource
{
public string name { get; set; }
}
}
...and then you can deserialize a Person object.
EDIT
Ok, given the sample json you provided in the updated question, here's how I wrote the classes to hold the response:
public class GoogleSearchItem
{
public string kind { get; set; }
public string title { get; set; }
public string link { get; set; }
public string displayLink { get; set; }
// and so on... add more properties here if you want
// to deserialize them
}
public class SourceUrl
{
public string type { get; set; }
public string template { get; set; }
}
public class GoogleSearchResults
{
public string kind { get; set; }
public SourceUrl url { get; set; }
public GoogleSearchItem[] items { get; set; }
// and so on... add more properties here if you want to
// deserialize them
}
And here's the C# code to deserialize:
// create a new instance of JavaScriptSerializer
JavaScriptSerializer s1 = new JavaScriptSerializer();
// deserialise the received response
GoogleSearchResults results = s1.Deserialize<GoogleSearchResults>(json);
Console.WriteLine(s1.Serialize(results));
Some comments:
The toplevel class to hold the search result is called GoogleSearchResults.
The first property in the GoogleSearchResults class is kind, corresponding to the first named property in the json object. You had link which isn't going to work, because link is not the name of a top-level property in that json object. There are properties lower in the hierarchy of your json named "link" but JavaScriptSerializer won't pull out those lower level things into the higher level.
The next property in my GoogleSearchResults class is of type SourceUrl. This is because the url property in the json is not a simple string - it is a json object with two properties, each with a string value. So SourceUrl as a class in C# gets two string properties, each with the appropriate name to deserialize one of those named properties.
the next property in the GoogleSearchResults class is called "items" so that it can deserialize the items dictionary from your json. Now items, as the name suggests, is an array in the json, as denoted by the square bracket around its value. This means there can be more than one item, although in your case there is just one item. So this property in C# must be an array (or collection). Each item in the json result is not a simple string, so, once again, as we did with SourceUrl, we need to define a holder class to deserialize the item object: GoogleSearchItem. This class has a bunch of simple string properties. The properties in the C# class could also be of type int or some other type, if that's what the json requires.
finally, when printing out the result, if you just call Console.WriteLine(result) you will see the result of the ToString() method that is implicitly invoked by Console.WriteLine. This will merely print the name of the type, in this case is "GoogleSearchResults", which is not what you want, I think. In order to see what's in the object, you need to serialize it, as I've shown. In the output of that, you will see only the values of things you deserialized. Using the classes I provided, the result will have less information than the original, because I didn't provide properties in the C# class corresponding to some of the json properties, so those weren't deserialized.
You could take a look at Json.NET and its LINQ support to create and query JSON. By crafting a nice LINQ query you will get only the stuff you need (you can select, group by, count, min, max, whatever you like).
http://msdn.microsoft.com/en-us/library/bb412170.aspx
http://msdn.microsoft.com/en-us/library/bb410770.aspx
Pull out the property you need after you have converted the JSON representation to a type in your C# app. I don't think there's a way to extract only one property from the JSON representation before you have converted it (though I am not sure).

Categories

Resources