JSON Root Node - Different From Class Name - c#

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

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).

Validate input to a POST request?

I have made a controller for a REST POST api endpoint, is as following
public IActionResult POST([FromBody]Person person)
{
....
}
and where Person is defined as
public class Person
{
public string Name {get; set;}
public int Age {get; set;}
}
meaning that the post request is able deserialise an input like this
{
"Name": "Peter",
"Age": 2
}
without any problems...
Problems occurs though when I pass something like
{
"Name": "Peter",
"Age": 2,
"Error": 123123123
}
It still creates an Person which have the first two filled out..
I would like to trigger an error here stating that the input format is wrong.
I am storing the first input - and then use it to compare the next input based on the first one.
If they are the same - nothing should happen.
But since the last input contains a invalid field but the actual instance the input created is completely similar to the first one - I get an error on they are not equal?
I assume the error statement in the JSON is somehow stored in the new instance?
but how do i make sure that the input is being validated before the controller created an instance given the JSON input?
1) to validate your Person model, you could have something like
using System.ComponentModel.DataAnnotations;
public class Person
{
[Required]
[StringLength(250, MinimumLength = 2)]
public string Name { get; set; }
[Required]
[Range(1, 121)]
public int Age { get; set; }
}
and inside controller's POST action you can use if(!ModelState.IsValid){.....}
2) to compare if two Person instances are equal you could override Equals():
public class Person
{
...
...
public override bool Equals(object obj) =>
(obj is Person otherPerson) ? (Name,Age) == (otherPerson.Name,otherPerson.Age): false;
}
here current instance's Name and Age are compared to other instance's Name and Age. It will allow using if ( person.Equals ( someOtherPErsonInstance ) ){....}
3) if you need to make sure extra fields are not present in the POST request, you could add custom implementation IModelBinder, or accept raw data in POST action, then, parse and check.

Using Automapper to map from dynamic/JObject to arbitrary types without creating multiple maps

In an ASP.NET Core controller method, which has a parameter of type dynamic, I would like to map using Automapper as shown below. The method looks like this:
public IActionRsult Post([FromBody] dynamic model) {
// Skip validation
switch(model.src) {
case "employer"
var employerVM = _mapper.Map<EmployerViewModel>(model.data);
// Work with mapped object
break;
case "employee"
var employeeVM = _mapper.Map<EmployeeViewModel>(model.data);
// Work with mapped object
break;
}
}
where EmployerViewModel looks like this:
public class EmployerViewModel {
public string CompanyName {get; set;}
public string CompanyAddress {get; set;}
}
and EmployeeViewModel looks like this:
public class EmployeeViewModel {
public string FirstName {get; set;}
public string LastName {get; set;}
public bool Ready {get; set;}
}
It receives JSON data from the client side, which may look like this:
{
"src": "employer",
"data": {
"CompanyName": "Pioneers Ltd.",
"CompanyAddress": "126 Schumacher St., London"
}
}
or this:
{
"src": "employee",
"data": {
"FirstName": "John",
"LastName": "Doe",
"Ready": true
}
}
Now everything works fine except for boolean properties, which are always set to false no matter what the value in JSON is. I have JSON input formatter, which constructs the instances in the parameter. I've checked the type of the instances and found them to be Newtonsoft.Json.Linq.JObject
Any idea how I can get bools to behave correctly?
I would like to continue supporting mapping an arbitrary number of destination classes using the TDestination IMapper.Map<TDestination>(object source) (see here) without having to explicitly construct AutoMapper maps for each. Any hint how this can be achieved?
P.S. I'm using AutoMapper 6.2.1 and ASP.NET Core 1.1.3
The problem is that JObject wraps its content in JValue, so it cannot work by default with AM which of course expects the actual values. So you have to let AM know how to map a JValue:
cfg.CreateMap<JValue, object>().ConvertUsing(source => source.Value);

Adding serialization information for MongoDB custom serializer

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.

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.

Categories

Resources