How to use Json.NET for model binding in mvc 5 - c#

I have the same issue as this guy posting single item in json array to .net object.
To summarize, I am sending JSON data to an action and getting null when there is only one item passed. But it works when there is more than one item.
I know this has to do with how the JSON string is constructed (list of object vs. single object) but I have no choice; I am using a limited jquery json library (that's another story)
JSON string example with a list of fields
{
"formtemplate":{
"fields":{
"field":[
{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980984393",
"_type":"text",
"_subtype":"text"
},
{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980999418",
"_type":"text",
"_subtype":"text"
}
]
}
}
}
JSON string example with only one field item
{
"formtemplate":{
"fields":{
"field":{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980984393",
"_type":"text",
"_subtype":"text"
}
}
}
}
Model
public class Fields
{
public List<Field> field { get; set; }
}
public class Formtemplate
{
public Fields fields { get; set; }
}
public class CustomFields
{
public Formtemplate formtemplate { get; set; }
}
public class Field
{
public string _label { get; set; }
public string _name { get; set; }
public string _type { get; set; }
public List<Option> option { get; set; }
}
Action
[HttpPost]
public JsonResult saveCustomfields(CustomFields customfields)
{
}
I followed the advice on the link provided but it didn't seem to work, here is what I did
public class Fields
{
private List<Field> field;
public object Numbers
{
get { return field; }
set
{
if (value.GetType() == typeof(Field))
{
field = new List<Field> { (Field)value };
}
else if (value.GetType() == typeof(List<Field>))
{
field = (List<Field>)value;
}
}
}
}
In the action I get both of "Numbers" and "field" as null when there is one item or more. The solution is logical but it didn't work for me.
Update
I researched a bit more and implemented a custom converter from this link How to handle both a single item and an array for the same property using JSON.net. But when I debug; it seems like SingleOrArrayConverter is not even called
public class Fields
{
[JsonProperty("field")]
[JsonConverter(typeof(SingleOrArrayConverter<Field>))]
public List<Field> field { get; set; }
}
public class Formtemplate
{
[JsonProperty("fields")]
public Fields fields { get; set; }
}
public class CustomFields
{
[JsonProperty("formtemplate")]
public Formtemplate formtemplate { get; set; }
}
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
But still no success, does it have to do with my action?

Related

Use Json Converter to add properties to Dictionary

I am deserializing some JSON endpoints to C# Objects via JArray.Parse() and have come across a schema I'm not sure how to handle. The nUnavailResReasonsMap Object below has a dynamic number of name/value pairs; all of the name values will be integers:
{
"id": "Customer Service",
"operation": "UPDATE",
"VoiceIAQStats": {
"id": 139,
"esdId": 139,
...
"nUnavailResources": 2,
"nUnavailResReasonsMap": {
"4": 1,
"9": 1
},
"nWorkResources": 0,
"nSelectedResources": 0,
...
"nSLAPercentageHighThreshold": 0
}
}
And here are my C# Objects:
//Root Level
public class QueueStats
{
public string Id { get; set; }
public string Operation { get; set; }
public VoiceIaqStats VoiceIaqStats { get ; set ; }
}
public class VoiceIaqStats
{
public int Id { get; set; }
public int EsdId { get; set; }
...
public int NUnavailResources { get; set; }
public NUnavailResReasonsMaps NUnavailResReasonsMap { get ; set ; }
public int NWorkResources { get; set; }
public int NSelectedResources { get; set; }
...
public int NSlaPercentageHighThreshold { get; set; }
}
[JsonConverter( typeof( QueueStatsConverter))]
public class NUnavailResReasonsMaps
{
public Dictionary<string ,int > NUnavailResReasonsMap { get; set; }
}
Per another SO Post, I have setup the below Json Converter, which is getting called but I'm not sure how to get the values into the Dictionary defined above.
public class QueueStatsConverter : JsonConverter
{
public override bool CanConvert( Type objectType)
{
return objectType.IsClass;
}
public override bool CanWrite => false;
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var instance = objectType.GetConstructor(Type .EmptyTypes)?.Invoke(null);
var props = objectType.GetProperties();
var jo = JObject.Load(reader);
foreach ( var jp in jo.Properties())
{
//I can see the properties, but how do I add them to my existing dictionary?
}
return instance;
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Is there a way to make this work or do I need to change my approach?
You don't need a special converter at all, just remove the NUnavailResReasonsMaps class and use a Dictionary<int, int> instead:
public class VoiceIaqStats
{
//snip
public Dictionary<int, int> NUnavailResReasonsMap { get; set; }
//snip
}
And the conversion will work out of the box:
var result = JsonConvert.DeserializeObject<QueueStats>(json);

Map JSON payload to DTO with different field names

I have a simple GitHub payload incoming to my ASP.NET Core application and I would like to know how can I map the payload I receive to my DTO.
Example DTO
public class GithubPayload
{
public string Action { get; set; } // action
public string Name { get; set; } // pull_request.title
}
Example payload
{
"action": "deleted",
"pull_request": {
"title": "Fix button"
}
}
You can use JsonProperty attribute on Action and a custom converter on the Name that can interpret nested properties. check Json.Net's JsonConverter
public class GithubPayload {
[JsonProperty("action")]
public string Action { get; set; }
[JsonConverter(typeof(NestedConverter), "pull_request.title")]
public string Name { get; set; }
}
Where NestedConverter is a custom JsonConverter that will read a nested property
public class NestedConverter : JsonConverter {
private readonly string path;
public NestedConverter (string path) {
this.path = path;
}
//...to do
}
Update:
Using a JsonConverter for converting the payload itself actually works as well
public class GithubPayloadConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(GithubPayload);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
dynamic data = JObject.Load(reader);
var model = new GithubPayload {
Action = data.action,
Name = data.pull_request.title
};
return model;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
and decorating the class itself
[JsonConverter(typeof(GithubPayloadConverter))]
public class GithubPayload {
public string Action { get; set; }
public string Name { get; set; }
}
Deserialization is simply
string json = #"{ 'action': 'deleted', 'pull_request': { 'title': 'Fix button' } }";
var payload = JsonConvert.DeserializeObject<GithubPayload>(json);
Just an idea:
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json;
using System.IO;
namespace WebApplication1
{
public class Program
{
public class GithubPayload
{
public string Action { get; set; } // action
public string Name { get; set; } // pull_request.title
}
public static void Main(string[] args)
{
string json = #"{
""action"": ""deleted"",
""pull_request"": {
""title"": ""Fix button""
}
}";
dynamic obj = JsonConvert.DeserializeObject(json);
GithubPayload entity = new GithubPayload();
entity.Action = obj.action;
entity.Name = obj.pull_request.title;
..................
}
}
}
Tested this solution, it works.

Deserialize nested ICollection<BaseType> in Asp.Net Web API 2 controller

I have a Web Api Controller like this one :
public IHttpActionResult Create(PaymentDTO Payment)
My DTOs are:
public class PaymentDTO
{
public int Id { get; set; }
public string type { get; set; }
public IEnumerable<TransactionDTO> Transactions { get; set; }
}
public class TransactionDTO
{
public int Id { get; set; }
public string Description { get; set; }
public string CreateTime { get; set; }
public string UpdateTime { get; set; }
}
public class SaleDTO : TransactionDTO
{
public string Total { get; set; }
public string Currency{ get; set; }
}
public class OrderDTO : TransactionDTO
{
public string State {get;set;}
}
I receive the following JSON formatted data :
{
"Type": "sale",
"Id": 101,
"transactions": [
{
"Total": "30.50",
"Currency": "USD",
"Description": "transaction description"
}
]
}
I want JSON.net to instantiate either a IEnumerable<SaleDTO> or IEnumerable<OrderDTO> based on the Type Property.
I could've used a custom type converter, but only if Type property was in TransactionDTO. But I want the Type property to be in the parent object (PaymentDTO)
Thank you in advance for your help.
You can do this with a custom JsonConverter on the PaymentDTO class:
public class PaymentDTOConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(PaymentDTO).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var payment = (PaymentDTO)existingValue ?? new PaymentDTO();
// Extract the transactions.
var transactions = obj.Property("transactions") ?? obj.Property("Transactions");
if (transactions != null)
transactions.Remove();
// Populate the remaining regular properties.
using (var subReader = obj.CreateReader())
serializer.Populate(subReader, payment);
if (transactions != null)
{
// Deserialize the transactions list.
var type = PaymentDTO.GetTransactionDTOType(payment.type) ?? typeof(TransactionDTO);
using (var subReader = transactions.Value.CreateReader())
// Here we are taking advantage of array covariance.
payment.Transactions = (IEnumerable<TransactionDTO>)serializer.Deserialize(subReader, type.MakeArrayType());
}
return payment;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then apply it to your PaymentDTO class as follows:
[JsonConverter(typeof(PaymentDTOConverter))]
public class PaymentDTO
{
static Dictionary<string, Type> namesToTransactions;
static Dictionary<Type, string> transactionsToNames = new Dictionary<Type, string>
{
{ typeof(SaleDTO), "sale" },
{ typeof(OrderDTO), "order" },
};
static PaymentDTO()
{
namesToTransactions = transactionsToNames.ToDictionary(p => p.Value, p => p.Key);
}
public static string GetTransactionDTOTypeName<TTransactionDTO>() where TTransactionDTO : TransactionDTO
{
string name;
if (transactionsToNames.TryGetValue(typeof(TTransactionDTO), out name))
return name;
return null;
}
public static Type GetTransactionDTOType(string name)
{
Type type;
if (namesToTransactions.TryGetValue(name, out type))
return type;
return null;
}
public int Id { get; set; }
public string type { get; set; }
[JsonProperty("transactions")]
public IEnumerable<TransactionDTO> Transactions { get; set; }
}

C# WebAPI Routing by POST value type

I have a request to create a Web API that is able to accept a POST request and take different actions depending on the type of data (DataAvailableNotification vs ExpiredNotification) received in the parameters.
I've created an ApiController and exposed two methods:
[HttpPost]
public void DataAvailable(DataAvailableNotification dataAvailable,
[FromUri] string book, [FromUri] string riskType)
{
}
[HttpPost]
public void DataAvailable(ExpiredNotification dataAvailable,
[FromUri] string book, [FromUri] string riskType)
{
}
public class DataAvailableNotification
{
[JsonProperty(PropertyName = "$type")]
public string RdfType { get { return "App.RRSRC.Feeds.DataAvailable"; } }
public string SnapshotRevisionId { get; set; }
public string[] URLs { get; set; }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
public class ExpiredNotification
{
[JsonProperty(PropertyName = "$type")]
public string RdfType { get { return "Service.Feeds.Expired"; } }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
However, they don't get called at all.
If I comment out one of them the notification reaches the controller but I cannot handle the notification type correctly (given that both notifications will map to the same method).
Is there any way configuring Web API to look into the type of the POSTed value and call the best matching controller method?
PS: I cannot have 2 different of URLs to handle the different notifications. So please don't suggest this.
Use one action and filter based on the type.
I got around the same issue by using reflection and doing something like the following.
[HttpPost]
public void DataAvailable([FromBody]IDictionary<string, string> dataAvailable,
[FromUri] string book, [FromUri] string riskType) {
if(dataAvailable != null && dataAvailable.ContainsKey("$type") {
var type = dataAvaliable["$type"];
if(type == "App.RRSRC.Feeds.DataAvailable"){
DataAvailableNotification obj = createInstanceOf<DataAvailableNotification>(dataAvailable);
DataAvailable(obj,book,riskType);
} else if (type == "Service.Feeds.Expired") {
ExpiredNotification obj = createInstanceOf<ExpiredNotification>(dataAvailable);
DataAvailable(obj,book,riskType);
}
}
}
private void DataAvailable(DataAvailableNotification dataAvailable, string book, string riskType) {
}
private void DataAvailable(ExpiredNotification dataAvailable, string book, string riskType) {
}
private T createInstanceOf<T>(IDictionary<string, string> data) where T : class, new() {
var result = new T();
var type = typeof(T);
//map properties
foreach (var kvp in data) {
var propertyName = kvp.Key;
var rawValue = kvp.Value;
var property = type.GetProperty(propertyName);
if (property != null && property.CanWrite) {
property.SetValue(result, rawValue );
}
}
return result;
}
The solution I settled with is similar to what #Nikosi and #jpgrassi suggested.
In the controller I've created a single notification point:
[HttpPost]
public void Notify(BaseNotification notification,
[FromUri] string book, [FromUri] string riskType)
{
DataAvailableNotification dataAvailableNotification;
ExpiredNotification expiredNotification;
if ((dataAvailableNotification = notification as DataAvailableNotification) != null)
{
HandleDataAvailableNotification(dataAvailableNotification);
}
else if ((expiredNotification = notification as ExpiredNotification) != null)
{
HandleExpiredNotification(expiredNotification);
}
}
private void HandleDataAvailableNotification(DataAvailableNotification dataAvailableNotification)
{
}
private void HandleExpiredNotification(ExpiredNotification expiredNotification)
{
}
BaseNotification is the base class for all notifications:
public abstract class BaseNotification
{
[JsonProperty(PropertyName = "$type")]
public abstract string RdfType { get; }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
Created a JsonConverter:
public class RdfNotificationJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var resultJson = JObject.Load(reader);
var rdfType = resultJson["$type"].ToObject<string>();
BaseNotification result;
switch (rdfType)
{
case "App.RRSRC.Feeds.DataAvailable":
{
result = new DataAvailableNotification
{
SnapshotRevisionId = resultJson["SnapshotRevisionId"].ToObject<string>(),
URLs = resultJson["URLs"].ToObject<string[]>()
};
break;
}
case "Service.Feeds.Expired":
{
result = new ExpiredNotification();
break;
}
default:
{
throw new NotSupportedException();
}
}
result.ChannelId = resultJson["ChannelId"].ToObject<Guid>();
result.ConsumerId = resultJson["ConsumerId"].ToObject<string>();
return result;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(BaseNotification);
}
}
And registered the new converter in the configuration:
public static void Configure(HttpSelfHostConfiguration config)
{
Throw.IfNull(config, "config");
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new RdfNotificationJsonConverter());
}
I like this solution better because I have the actual type in the controller and the converter handles the ugly deserialization part (also more testable).
PS: I'll move the literal strings somewhere else so I don't specify them twice in the solution.

JSON.Net Deseralize json pair to Object Propery

Ok I have WebApi application that is sending back name value pairs like so
{'FirstName':'SomeGuy'}
On the server the FirstName field is not just a string, it is a generic object that hold additional information about FirstName, and is not send back from the client.
Here is a outline of the classes
public abstract class Field
{
protected object _value;
......More Properties/Methods
public bool HasValue
{
get { return _hasValue; }
}
public object Value
{
get { return _hasValue ? _value : null; }
}
protected void SetValue(object value, bool clearHasValue = false)
{
_value = value;
_hasValue = clearHasValue ?
false :
value != null;
}
}
public class Field<T> : Field
{
..Constructors and methods
public new T Value
{
get { return _hasValue ? (T)_value : default(T); }
set { SetValue(value); }
}
}
So.. In theory I may be trying to bind to a model like
class FieldModel
{
public Field<string> FirstName { get; set; }
public Field<string> LastName { get; set; }
public Field<Decimal> Amount { get; set; }
public FieldModel()
{
FirstName = new Field<string>();
LastName = new Field<string>();
Amount = new Field<decimal>();
}
}
So here is the issue.. I want FirstName in my json object to deseralize to right property. Now if I modify the json package to {'FirstName.Value':'SomeGuy'} JSON.net works out of the box, but I really not to do that. I have been tying to make my own JsonConverter but have not been able to get that to work. So, I don't think this should be very hard, but I am a bit stuck.
EDIT
So.. I did come up with a solution that works, but I have to think there is a better way.. It uses dynamics and I have to think that I am missing an easy solution.
public class FieldConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var internalVal = serializer.Deserialize(reader, objectType.GetGenericArguments().FirstOrDefault());
var retVal = existingValue as dynamic;
retVal.Value = internalVal as dynamic;
return retVal;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType.IsSubclassOf(typeof(Field));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can easily do this with JSON.NET's CustomCreationConverter. Here's an example:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
public class Employee : Person
{
public string Department { get; set; }
public string JobTitle { get; set; }
}
public class PersonConverter : CustomCreationConverter<Person>
{
public override Person Create(Type objectType)
{
return new Employee();
}
}
And the usage:
string json = #"{
'Department': 'Furniture',
'JobTitle': 'Carpenter',
'FirstName': 'John',
'LastName': 'Joinery',
'BirthDate': '1983-02-02T00:00:00'
}";
Person person = JsonConvert.DeserializeObject<Person>(json, new PersonConverter());
Console.WriteLine(person.GetType().Name);
// Employee
Employee employee = (Employee)person;
Console.WriteLine(employee.JobTitle);
// Carpenter

Categories

Resources