Custom model binding - ASP.NET Core - c#

I have this incoming request with this payload
{
count : 10,
supplier : {
id : 342,
name : 'test'
},
title : 'some title'
}
and I have this model in my c# code
class SomeModel
{
public string Title { get; set; }
public int SupplierId { get; set; }
public double Amount { get; set; }
}
This is my controller method
public IActionResult Foo(SomeModel data)
{
//...
}
I would like to map the property count in request payload to Amount property in my c# model and mapping the value of supplier.id into SupplierId.
I'm using Newtonsoft Json.NET library for binding

Obviously the simplest way is to create a class corresponding to payload stucture like this
public class SomeModel
{
public string Title { get; set; }
public double Count { get; set; }
public Supplier Supplier { get; set; }
}
public class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
}
Another iteration could be using JsonProperty attribute for Amount and SupplierId property making use of Supplier
class SomeModel
{
public string Title { get; set; }
[JsonProperty("count")]
public double Amount { get; set; }
public int SupplierId => Supplier.Id;
public Supplier Supplier { get; set; }
}
class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
}
But if you like to stick with your current model you will need to create a custom converter. And what I can suggest you
public class NestedPropertyConverter : JsonConverter
{
private string[] _properties;
public NestedPropertyConverter(string propertyChain)
{
//todo: check if property chain has valid structure
_properties = propertyChain.Split('.');
}
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = (JToken)serializer.Deserialize(reader);
foreach (string property in _properties)
{
token = token[property];
if (token == null) //if property doesn't exist
return existingValue; //or throw exception
}
return token.ToObject(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Basically, the converter allows to bind nested properties. Usage
class SomeModel
{
public string Title { get; set; }
[JsonConverter(typeof(NestedPropertyConverter), "id")]
[JsonProperty("supplier")] //its necessary to specify top level property name
public int SupplierId { get; set; }
[JsonProperty("count")]
public double Amount { get; set; }
}
Note
I also tested the following payload
{
count : 10,
supplier : {
id : 342,
name : "test",
test: {
val: 55
}
},
title : "some title"
}
and config for property
[JsonConverter(typeof(NestedPropertyConverter), "test.val")]
[JsonProperty("supplier")]
public int SupplierId { get; set; }
and it works fine.

Related

Deserialize JSON array which has mixed values System.Text.JSON

I'm trying to create a page rendered in .net core 3.1 which renders pages based on JSON.
How can I deserialzie the JSON at the end of this post?
I've tried to deserialize this however it doesn't work because I loose the data for each Component,
since the Page class has a List<Component> - but I need this to be a list of varying different components.
Page Model :
public class Page
{
public int id { get; set; }
public string pagename { get; set; }
public string metatitle { get; set; }
public string metadescription { get; set; }
public string created_at { get; set; }
public string updated_at { get; set; }
public List<Component> components { get; set; }
}
public class Pages
{
public List<Page> pages { get; set; }
}
Component Model:
public class Component
{
public string component { get; set; }
public int id { get; set; }
}
A Component :
public class Title : Component
{
public string component { get; set; }
public int id { get; set; {
public string titletext { get; set; }
}
This is the JSON:
{
"id":1,
"pagename":"home",
"metatitle":"no title",
"metadescription":"no meta",
"created_at":"2020-05-31T16:35:52.084Z",
"updated_at":"2020-05-31T16:35:52.084Z",
"components":[
{
"component":"components.titletext",
"id":1,
"titletext":"hello"
},
{
"component":"components.section",
"id":2,
"titletext":"hello",
"descriptiontext":"its a beatiful day in lost santos",
"buttonlink":"/go"
},
{
"component":"components.cta",
"id":3,
"sectiontitle":"hello",
"buttonlink":"/go",
"buttontext":"click me"
}
]
}
If you don't want to add all properties to the Component class like that:
public class Component
{
public string component { get; set; }
public int id { get; set; }
public string titletext { get; set; }
public string sectiontitle { get; set; }
public string buttonlink { get; set; }
public string descriptiontext { get; set; }
}
You will need to write custom JsonConverter for example (not very performant implementation but works with your json and you will not need to parse every field by hand):
public class ComponentConverter : JsonConverter<Component>
{
public override Component Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (var doc = JsonDocument.ParseValue(ref reader))
{
var type = doc.RootElement.GetProperty(#"component").GetString();
switch(type)
{
case "components.titletext":
return JsonSerializer.Deserialize<Title>(doc.RootElement.GetRawText());
// other types handling
default: return JsonSerializer.Deserialize<Component>(doc.RootElement.GetRawText());
}
}
}
public override void Write(Utf8JsonWriter writer, Component value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
public class Component
{
public string component { get; set; }
public int id { get; set; }
}
public class Title : Component
{
public string titletext { get; set; }
}
And usage example:
var json = #"[
{
""component"":""components.titletext"",
""id"":1,
""titletext"":""hello""
},
{
""component"":""components.section"",
""id"":2,
""titletext"":""hello"",
""descriptiontext"":""its a beatiful day in lost santos"",
""buttonlink"":""/go""
},
{
""component"":""components.cta"",
""id"":3,
""sectiontitle"":""hello"",
""buttonlink"":""/go"",
""buttontext"":""click me""
}
]";
var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new ComponentConverter());
JsonSerializer.Deserialize<List<Component>>(json, deserializeOptions).Dump();
Also do not use this converter as parameter for JsonConverterAttribute cause it will end in stackoverflow.
If you want/need completely separate unrelated classes you could use a technique that doesn't use a converter:
var ays = new List<A>();
var bees = new List<B>();
using var doc = JsonDocument.Parse(json);
foreach (var block in doc.RootElement.EnumerateArray())
{
switch (block.GetProperty("component").GetString())
{
case "typeA": ays.Add(Deserialise<A>(block)); break;
case "typeB": bees.Add(Deserialise<B>(block)); break;
// ... case
//default: ...
}
}
var composite = new
{
As = ays,
Bs = bees
};
// This is OK, but if you need to speed it up, please have a look at
// https://stackoverflow.com/questions/58138793/system-text-json-jsonelement-toobject-workaround
static T Deserialise<T>(JsonElement e) => JsonSerializer.Deserialize<T>(e.GetRawText(), options: new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

How to deserialize interfaces with Newtonsoft Json.Net

I have this class hierarchy :
public class ProxyBotsSnapshotLogEntryDetails : IBotsSnapshotLogEntryDetails
{
public ICollection<IBotSnapshot> Snapshots { get; set; }
}
public class ProxyBotSnapshot : IBotSnapshot
{
public string Name { get; set; }
public ICollection<IBotSnapshotItem> States { get; set; }
}
public class ProxyBotSnapshotItem : IBotSnapshotItem
{
public int Count { get; set; }
public IrcBotChannelStateEnum State { get; set; }
}
and their corresponding interfaces
public interface IBotsSnapshotLogEntryDetails
{
ICollection<IBotSnapshot> Snapshots { get; set; }
}
public interface IBotSnapshot
{
string Name { get; set; }
ICollection<IBotSnapshotItem> States { get; set; }
}
public interface IBotSnapshotItem
{
int Count { get; set; }
IrcBotChannelStateEnum State { get; set; }
}
that I would like to deserialize from JSON:
var test = JsonConvert.DeserializeObject<ProxyBotsSnapshotLogEntryDetails>(entry.DetailsSerialized);
but I get an error saying that Newtonsoft cannot convert interfaces.
I found this promising article:
https://www.c-sharpcorner.com/UploadFile/20c06b/deserializing-interface-properties-with-json-net/
but am not sure how to use the attribute, since in my case, the property is a list of interface.
The converter provided in the article works super nicely, I was just missing the syntax to use it on a collection property. Here is the code with the converter and the working attributes:
// From the article
public class ConcreteConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
public class ProxyBotsSnapshotLogEntryDetails : IBotsSnapshotLogEntryDetails
{
[JsonProperty(ItemConverterType = typeof(ConcreteConverter<ProxyBotSnapshot>))]
public ICollection<IBotSnapshot> Snapshots { get; set; }
}
public class ProxyBotSnapshot : IBotSnapshot
{
public string Name { get; set; }
[JsonProperty(ItemConverterType = typeof(ConcreteConverter<ProxyBotSnapshotItem>))]
public ICollection<IBotSnapshotItem> States { get; set; }
}
public class ProxyBotSnapshotItem : IBotSnapshotItem
{
public int Count { get; set; }
public IrcBotChannelStateEnum State { get; set; }
}
Maybe it works if you add the following settings to the Serializer and Deserializer methods:
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}
For example:
var test = JsonConvert.DeserializeObject<ProxyBotsSnapshotLogEntryDetails>(entry.DetailsSerialized,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});

C# deserialize Json based on condition

{
"timeAgo": "6 minutes ago",
"time": "07/11/2016 07:00 AM",
"alertId": 145928,
"details": {
},
"priority": 10,
"type": 2,
"isClosed": 0,
"notesCount": 0,
"patientAccountId": 680,
"isRead": 0
}
I want to deserialize the json based on the int value 'type', in such as way, I want the details to be different types
public class Notification
{
public string timeAgo { get; set; }
public string time { get; set; }
public int alertId { get; set; }
public object details { get; set; }
public int priority { get; set; }
public int type { get; set; }
public int isClosed { get; set; }
public int notesCount { get; set; }
public int patientAccountId { get; set; }
public int isRead { get; set; }
}
if type = 1, then the object 'details' is of type A, if type = 2, 'details' is of type B and so on. There are about 25 values for type.
So, later I can do something like:
Notification n = ....
if (type == 1)
{
A a = (a) n.details;
If your json does not have appropriate typing included in the JSON, this will work.
This may need tweaking if your actual structure is more complex, but I managed to get this to work on your sample.
var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<Notification>(
js,
new ItemConverter());
public class ItemA : Item { }
public class ItemB : Item { }
public class Item { }
public class Notification
{
public string timeAgo { get; set; }
public string time { get; set; }
public int alertId { get; set; }
public Item details { get; set; }
public int priority { get; set; }
public int type { get; set; }
public int isClosed { get; set; }
public int notesCount { get; set; }
public int patientAccountId { get; set; }
public int isRead { get; set; }
}
public class ItemConverter : JsonConverter
{
private Type currentType;
public override bool CanConvert(Type objectType)
{
return typeof(Item).IsAssignableFrom(objectType) || objectType == typeof(Notification);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
if (item["type"] != null)
{
// save the type for later.
switch (item["type"].Value<int>())
{
case 1:
currentType = typeof(ItemA);
break;
default:
currentType = typeof(ItemB);
break;
}
return item.ToObject<Notification>();
}
// use the last type you read to serialise.
return item.ToObject(currentType);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can use special JSON serializer settings, like
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
Type name will be stored with actual "details" data, so you can deserialize it to valid class.
var instance = new Notification
{
details = new Details
{
Name = "Hello"
}
};
var json = JsonConvert.SerializeObject(instance, jsonSerializerSettings);
Produced json
{"$type":"Application.Notification, Application","details":{"$type":"Application.Details, Application","Name":"Hello"}}
var json = "{\r\n \"timeAgo\": \"6 minutes ago\",\r\n \"time\": \"07/11/2016 07:00 AM\",\r\n \"alertId\": 145928,\r\n \"details\": {\r\n\r\n\r\n },\r\n \"priority\": 10,\r\n \"type\": 2,\r\n \"isClosed\": 0,\r\n \"notesCount\": 0,\r\n \"patientAccountId\": 680,\r\n \"isRead\": 0\r\n }";
Notification data = JsonConvert.DeserializeObject<Notification>(json);
var type=1;
if (type == 1)
{
string[] a = data.details as string[];
}

Custom JSon convertion to C# objects

I am getting JSon from a third party API which does not match my classes.
Some JSon properties are not be be converted others has different names, etc.
How can I define a custom conversion from the JSON to my C# object.
These are the objects I have in C#:
public class PropertyList {
public Int32 Page { get; set; }
public String[] ErrorMessages { get; set; }
public List<Property> Properties { get; set; }
}
public class Property {
public String Name { get; set; }
public String Reference { get; set; }
public String Region { get; set; }
public IList<String> Features { get; set; }
public String Id { get; set; }
public Double Price { get; set; }
public IList<String> ImagesUrls { get; set; }
}
And this is the JSon data which I want to convert from:
{
"page" : 0,
"errorMessages" : [ ],
"listings" : [
{
"data" : {
"name" : "The name",
"reference__c" : "ref1234",
"region__c" : "London",
"features__c" : "Garage;Garden;",
"id" : "id1234",
"price_pb__c" : 700000,
},
"media" : {
"images" : [
{
"title" : "image1",
"url" : "http://www.domain.com/image1"
},
{
"title" : "image2",
"url" : "http://www.domain.com/image2"
}
]
}
}
{
NOTE: Other items
}
]
}
How should I do this?
UPDATE 1
Using Thiago suggestion I was able to parse Page and ErrorMessages but not Properties. So I create the following converter:
public class PropertyResultConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return (objectType == typeof(PropertyResult));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
PropertyResult propertyResult = jo.ToObject<PropertyResult>();
propertyResult.Properties = jo.SelectToken("listings.data").ToObject<List<Property>>();
return propertyResult;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
But when I try this I get the following error:
An exception of type 'System.NullReferenceException' occurred
On:
propertyResult.Properties = jo.SelectToken("listings.data").ToObject<List<Property>>();
Any idea why this happens?
Miguel, have you tried http://www.newtonsoft.com/json ?
You can do the mappings of your code using C# attributes, like the snippets below.
public class PropertyList {
[JsonProperty("page")]
public Int32 Page { get; set; }
[JsonProperty("errorMessages")]
public String[] ErrorMessages { get; set; }
[JsonProperty("listings")]
public List<Property> Properties { get; set; }
}
public class Property {
[JsonProperty("name")]
public String Name { get; set; }
[JsonProperty("reference__c")]
public String Reference { get; set; }
[JsonProperty("region__c")]
public String Region { get; set; }
[JsonProperty("features__c")]
public IList<String> Features { get; set; }
[JsonProperty("id")]
public String Id { get; set; }
[JsonProperty("price_pb__c")]
public Double Price { get; set; }
[JsonProperty("media")]
public IList<String> ImagesUrls { get; set; }
}
I'm not sure if IList will work as you are expecting. I believe you will need to adapt it to Dictionary.
You can take a look on the API:
http://www.newtonsoft.com/json/help/html/SerializationAttributes.htm
UPDATE
Try to parse 'data' using the snippet below:
JObject jsonObject = JObject.Parse(json);
IList<JToken> results = jsonObject["listings"]["data"].Children().ToList();
IList<Property> processedList = new List<Property>();
foreach (JToken item in results)
{
Property propertyItem = JsonConvert.DeserializeObject<Property>(item.ToString());
processedList.Add(propertyItem);
}
I ran that snippet and it worked. You can explore the code that I generated on my github:
https://github.com/thiagoavelino/VisualStudio_C/blob/master/VisualStudio_C/StackOverFlow/ParsingJason/ParsingJson.cs

Deserialize Json objects with Json.Net

I'm trying to deserialize an object with Json.net. I was able to do it successfully but its more of a hack so I'm looking for a better/proper way to do so.
{
"page":"admin",
"context":"id",
"records":{
"rCount":6,
"1":[
{
"name":"Romeo",
"dob":"01\/01\/1970"
},
{
"name":"Harry",
"dob":"10\/10\/2012"
},
{
"name":"Lee",
"dob":"17\/10\/2012"
}],
"2":[
{
"name":"Mark",
"dob":"01\/01\/1970"
},
{
"name":"Jack",
"dob":"10\/10\/2012"
},
{
"name":"Json",
"dob":"17\/10\/2012"
}],
}}
this is the json string the issue is with the records object. if it doesn't have that rCount variable then it can be deserialized as a Dictionary but because of the rCount variable it can't be deserialized properly as a dictionary. What should be the proper way of deserialzing this object.
Here is my Solution:
class Program
{
static void Main(string[] args)
{
var recordFile = JsonConvert.DeserializeObject<RecordFile>(Properties.Resources.data);
}
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Records Records { get; set; }
}
public class Records
{
public int RCount { get; set; }
[JsonExtensionData]
private Dictionary<string, object> _reocordList;
public List<Record[]> RecordList
{
get
{
if (_reocordList != null && _reocordList.Count > 0)
{
return _reocordList.Values.Select(record => JsonConvert.DeserializeObject<Record[]>(record.ToString())).ToList();
}
return new List<Record[]>();
}
}
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
}
You can use jObject to manually parse JSON:
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Records Records { get; set; }
}
public class Records
{
public int RCount { get; set; }
public IDictionary<string, List<Record>> RecordsDictionary { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Then:
var jObject = JObject.Parse(\* your json *\);
var recordFile = new RecordFile
{
Page = jObject.Value<string>("page"),
Context = jObject.Value<string>("context"),
Records = new Records
{
RCount = jObject["records"].Value<int>("rCount"),
RecordsDictionary =
jObject["records"].Children<JProperty>()
.Where(prop => prop.Name != "rCount")
.ToDictionary(prop => prop.Name),
prop =>
prop.Value.ToObject<List<Record>>())
}
};
Of course it can be easily to handle cases, when property is not present.
I'm assuming you want to get rid of the Records class completely, ending up with something like this:
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public Dictionary<string, Record[]> Records { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Since you don't care at all about the records.rCount property from the JSON, you could specify a new error handler to simply ignore the property:
var recordFile = JsonConvert.DeserializeObject<RecordFile>(
jsonString,
new JsonSerializerSettings
{
Error = (sender, args) =>
{
if (args.ErrorContext.Path == "records.rCount")
{
// Ignore the error
args.ErrorContext.Handled = true;
}
}
});
Now when an error is encountered with the property in question, the deserializer will just skip over it. Another option would be to write a custom converter, but that feels like overkill just to ignore one property.
Example: https://dotnetfiddle.net/3svPqk
The other answers posted so far should both work. For completeness, I'll show how you can use a custom JsonConverter to solve this and also simplify your model a bit.
Here is the code for the converter:
class RecordFileConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(RecordFile));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
RecordFile rf = new RecordFile();
rf.Page = (string)jo["page"];
rf.Context = (string)jo["context"];
JObject records = (JObject)jo["records"];
rf.RecordCount = (int)records["rCount"];
rf.Records = records.Properties()
.Where(p => p.Name != "rCount")
.Select(p => p.Value.ToObject<Record[]>())
.ToList();
return rf;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To work with the converter, change your model classes as shown below. (Notice that the RecordFile class has a JsonConverter attribute to link the custom RecordFileConverter to it. Also notice the Records class is deleted, while the RCount and RecordList properties have been moved up to the RecordFile class and renamed.)
[JsonConverter(typeof(RecordFileConverter))]
public class RecordFile
{
public string Page { get; set; }
public string Context { get; set; }
public int RecordCount { get; set; }
public List<Record[]> Records { get; set; }
}
public class Record
{
public string Name { get; set; }
public string Dob { get; set; }
}
Then, deserialize as normal:
var recordFile = JsonConvert.DeserializeObject<RecordFile>(json);
Demo: https://dotnetfiddle.net/jz3zUT

Categories

Resources