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.
Related
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.
I'm trying to construct a request body for a REST api call, and I need to create a JSON object with the list of properties I want to get back.
For eg: I have this C# object that I want to get back:
public class SomeProperties
{
public string TicketNumber { get; set; }
public Driver Driver { get; set; }
}
public class Driver
{
public string Name { get; set; }
}
To get this back, I need to put these properties in a JSON request body like this:
"properties": [
"ticketNumber",
"driver.name"
]
My attempt looks like this:
private string FetchProperties()
{
var fetchProperties = new
{
properties = new List<string>
{
"ticketNumber",
"driver.name"
}
};
var jsonResult = JsonConvert.SerializeObject(fetchProperties, Formatting.None);
return jsonResult;
}
But I don't want to hard code the properties like that.
So is there any way I can use property names from the object I want, to put in the list of strings that I made in the method above?
Thank You!
If I understand correctly,you need Metadata of model.
if you use EntityFramework, you can get metadata of your model
from this Code
and call BuildJsonMetadata() function
and if you use other mapper, I dont see any exist tool for generate metadata of model and you must generate it handly
somthing like this
First of, if you serialize the class you have (SomeProperties), you will not get driver.name. Instead you will get a string like this one that shows driver as an object,
{
properties : {
"ticketNumber" : "stringvalue",
"driver" : {
"name" : "stringValue"
}
}
}
That said, if you are interested in getting a json like this,
"properties": [
"ticketNumber",
"driver.name"
]
you will need a class (very simple one at that) that contains only a list of strings. properties is not an array of objects, but simply strings. From the looks of the FetchProperties method, you are creating an object with fetchProperties as the RootObject. Try something like this,
public class MyClass
{
[JsonProperty("fetchProperties")]
public Fetch FetchProperties { get; set; }
}
public class Fetch
{
[JsonProperty("properties")]
public List<string> Properties { get; set; }
}
private string FetchProperties()
{
MyClass obj = new MyClass()
{
FetchProperties = new Fetch()
{
Properties = new List<string>() { "ticketNumber", "driver.Name" }
}
};
return JsonConvert.SerializeObject(obj); // Formatting.None is by default
}
Now its your choice to hard code these values or, pass them as arguments or use a local variable that contains a list of all the strings you intend to store as "properties". You cant use enums because of violation in naming convention (driver.name) so these options should suffice.
I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.
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
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.