JSON not binding correctly in WebApi action - c#

I'm creating and api using asp.net webapi
For some reasons this json:
JSON => {"page":"1","count":"10","sorting[name]":"asc"}
JSONString = >"{\"page\":\"1\",\"count\":\"10\",\"sorting[name]\":\"asc\"}"
Does not get bindend correctly to such a model:
public class DataSourceRequestParams
{
public int Page { get; set; }
public int Count { get; set; }
public IDictionary<string, string> Sorting { get; set; }
public IDictionary<string, string> Filter { get; set; }
public IDictionary<string, string> Order { get; set; }
}
The Sorting property does not get binded.
[HttpPost]
public PagingResult<ApplicationUser> Get([FromBody] DataSourceRequestParams #params)
{...}
If I create an action in an MVC application and pass the same JSON it gets binded.
Am I missing something here?

The binding in Web API expects a Dictionary as a JSON object when the Content-Type is application/json so the following JSON will bind correctly with your model:
{"page":"1","count":"10","sorting":{"name":"asc"}}
Edit
Give that you can't change the JSON being passed in you will need to manually bind the JSON to your object. There are a couple of options here. You could write your own IModelBinder or you could override the behaviour of the JSON binder by writing your own JsonConverter.
As a rough example, you could create a JsonConverter like this:
public class DictionaryConverter : JsonConverter
{
public DictionaryConverter()
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I've not implemented writing the Json
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//create a new object
var temp = new DataSourceRequestParams();
temp.Sorting = new Dictionary<string, string>();
temp.Filter = new Dictionary<string, string>();
temp.Order = new Dictionary<string, string>();
//load the input into a JObject and grab the "simple" values
JObject jsonObject = JObject.Load(reader);
temp.Page = jsonObject["page"].Value<int>();
temp.Count = jsonObject["count"].Value<int>();
//parse the dictionary values
AddValuesToDictionary(temp, jsonObject, "sorting", temp.Sorting);
AddValuesToDictionary(temp, jsonObject, "filter", temp.Filter);
AddValuesToDictionary(temp, jsonObject, "order", temp.Order);
return temp;
}
private void AddValuesToDictionary(DataSourceRequestParams test, JObject jsonObject, string name, IDictionary<string, string> dictionary)
{
//grab each matching property
var properties = jsonObject.Properties().Where(j => j.Name.StartsWith(name));
if (properties != null)
{
foreach (var property in properties)
{
/*for each matched property grab the value between the brackets
* from the name and the property value
* and the associate json value and add it to the dictionary
*/
dictionary.Add(Regex.Match(property.Name, #"\[([^\]]*)\]").Groups[1].Value, property.Value.Value<string>());
}
}
}
public override bool CanConvert(Type objectType)
{
//we can convert if the type is DataSourceRequestParams
return typeof(DataSourceRequestParams).IsAssignableFrom(objectType);
}
}
Then register the new converter in the GlobalConfiguration:
GlobalConfiguration.Configuration.Formatters.JsonFormatter
.SerializerSettings.Converters.Add(new DictionaryConverter());
Then your original JSON will parse correctly. Obviously that code is devoid of all error handling etc so it's not production ready.

Related

Deserializing json - mapping single property with no direct json match

I am trying to deserialize an existing JSON structure to into an object composed of a set of models. The naming in these models are not consistent and I was specifically asked to not change them (renaming, adding attributes, etc).
So, given this Json text (just a small sample):
{
"parameter": {
"alarms": [
{
"id": 1,
"name": "alarm1",
"type": 5,
"min": 0,
"max": 2
}],
"setting-active": true,
"setting-oneRun": true
}
}
would need to be mapped into these models:
public class Alarm
{
public int AlarmId { get; set; }
public string AlarmName { get; set; }
public AlarmType RbcType { get; set; }
public int MinimumTolerated { get; set; }
public int MaximumTolerated { get; set; }
}
public class Setting
{
public bool Active { get; set; }
public bool OneRun { get; set; }
}
public class Parameter
{
public List<Alarm> Alarms { get; set; }
public Setting ParameterSetting { get; set; }
}
So far, im writing a class that extends DefaultContractResolver and overrides maps property names.
MyCustomResolver so far:
public class MyCustomResolver : DefaultContractResolver
{
private Dictionary<string, string>? _propertyMappings;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
//ModelMappings is a static class that will return a dictionary with mappings per ObjType being deserialized
_propertyMappings = ModelMappings.GetMapping(type);
return base.CreateProperties(type, memberSerialization);
}
protected override string ResolvePropertyName(string propertyName)
{
if (_propertyMappings != null)
{
_propertyMappings.TryGetValue(propertyName, out string? resolvedName);
return resolvedName ?? base.ResolvePropertyName(propertyName);
}
return base.ResolvePropertyName(propertyName);
}
}
Code that Im using to deserialize:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new MyCustomResolver();
Parameter p = JsonConvert.DeserializeObject<Parameter>(jsonString, settings);
So I reached a point I need to somehow map the properties in Parameter to values located in the prev json node ("setting-active", "setting-oneRun"). I need to tell the deserializer where these values are.
Can this be done using an extension of DefaultContractResolver ?
I appreciate any tips pointing in the right direction
You can apply ModelMappings.GetMapping(objectType) in DefaultContractResolver.CreateObjectContract():
public class MyCustomResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
var overrides = ModelMappings.GetMapping(objectType);
if (overrides != null)
{
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.UnderlyingName != null && overrides.TryGetValue(property.UnderlyingName, out var name))
property.PropertyName = name;
}
}
return contract;
}
}
Notes:
By applying the mappings in CreateObjectContract() you can remap both property names and creator parameter names.
Since the contract resolver is designed to resolve contracts for all types, storing a single private Dictionary<string, string>? _propertyMappings; doesn't really make sense.
Unlike your previous question, your current question shows properties from a nested c# object ParameterSetting getting percolated up to the parent object Parameter. Since a custom contract resolver is designed to generate the contract for a single type, it isn't suited to restructuring data between types. Instead, consider using a DTO or converter + DTO in such situations:
public class ParameterConverter : JsonConverter<Parameter>
{
record ParameterDTO(List<Alarm> alarms, [property: JsonProperty("setting-active")] bool? Active, [property: JsonProperty("setting-oneRun")] bool? OneRun);
public override void WriteJson(JsonWriter writer, Parameter? value, JsonSerializer serializer)
{
var dto = new ParameterDTO(value!.Alarms, value.ParameterSetting?.Active, value.ParameterSetting?.OneRun);
serializer.Serialize(writer, dto);
}
public override Parameter? ReadJson(JsonReader reader, Type objectType, Parameter? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<ParameterDTO>(reader);
if (dto == null)
return null;
existingValue ??= new ();
existingValue.Alarms = dto.alarms;
if (dto.Active != null || dto.OneRun != null)
existingValue.ParameterSetting = new () { Active = dto.Active.GetValueOrDefault(), OneRun = dto.OneRun.GetValueOrDefault() };
return existingValue;
}
}
If your "real" model is too complex to define a DTO, you could create a JsonConverter<Paramater> that (de)serializes the JSON into an intermediate JToken hierarchy, then restructures that. See e.g. this answer to Can I serialize nested properties to my class in one operation with Json.net?.
In some cases, the custom naming of your properties is just camel casing. To camel case property names without the need for explicit overrides, set MyCustomResolver.NamingStrategy to CamelCaseNamingStrategy e.g. as follows:
var settings = new JsonSerializerSettings
{
DateFormatString = "YYYY-MM-DD",
// Use CamelCaseNamingStrategy since many properties in the JSON are just camel-cased.
ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() },
Converters = { new ParameterConverter() },
};
Demo fiddle here.
I think that the best way to "KEEP IT SIMPLE", you need to define an object that has exactly the properties of the json. Then you can use a library like "Automapper" to define rules of mapping between the "json object" and the "business object".

WEB API to return Nested Array JSON Result C#

Hi Request your help in Below Scenario.
I have a Requirement where in I want to return Response JSON from DotNet WEB API As below.
{ "Status":"OK", "Series":[["GREEN",40.5],["RED",12.8],["YELLOW",12.8]] }
Below are my Code Sample and Output.
public class Response
{
public string Status {get;set;}
public List<SampleData> Series {get;set;}
}
public class SampleData
{
public string ColorCode{get;set;}
public double ColorVal{get;set;}
}
API Get Method
public Response Get()
{
Response Objresponse = new Response();
Objresponse.Status ="OK";
List<SampleData> _sampleList = new List<SampleData>();
_sampleList.Add(new SampleData{ ColorCode="GREEN", ColorVal=40.5});
_sampleList.Add(new SampleData{ ColorCode="RED",ColorVal=12.8});
_sampleList.Add(new SampleData{ ColorCode="YELLOW",ColorVal=12.8});
Objresponse.Series = _sampleList;
return Objresponse;
}
JSON Output which is returned
{
"Status":"OK",
"Series":[
{"ColorCode":"GREEN","ColorVal":40.5},
{"ColorCode":"RED","ColorVal":12.8},
{"ColorCode":"YELLOW","ColorVal":12.8}
]
}
Request your help to return the output as per my requirement.
Thank you.......
Probably the easiest way would be to create a custom JsonConverter and transform your response inside to your required format:
public class ResponseConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Response);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Response originalResponse = (Response)value;
writer.WriteStartObject();
writer.WritePropertyName(nameof(originalResponse.Status));
writer.WriteValue(originalResponse.Status);
writer.WritePropertyName(nameof(originalResponse.Series));
writer.WriteStartArray();
foreach (var val in originalResponse.Series)
{
writer.WriteStartArray();
writer.WriteValue(val.ColorCode);
writer.WriteValue(val.ColorVal);
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
Then, you can decorate your Response class with a special attribute to denote that it should be serialized with this converter:
[JsonConverter(typeof(ResponseConverter))]
public class Response
{
public string Status { get; set; }
public List<SampleData> Series { get; set; }
}

Deserialize an Anonymous Type From a Collection

I have my serialized JSON in this format:
string json = #"[{"Name": "std_id","Value": "111"}, {"Name": "cust_id","Value": "444"}]"
How do I deserialize it to a single anonymous object like this:
var paramObj = new {"std_id" = 111, "cust_id" = 444}
Since you said the values of the Name and Value properties in your JSON objects can vary, you will not be able to deserialize to an anonymous object. Anonymous types are defined at compile-time, which means you need to know the property names ahead of time to be able to define them. The only way to get around that is code generation, which I think is going to be overkill for this situation. Instead, I would suggest you deserialize into a JObject with a dynamic variable. This will get you pretty close to what you want. Here's how:
string json = #"[
{ ""Name"": ""std_id"", ""Value"": ""111"" },
{ ""Name"": ""cust_id"", ""Value"": ""444"" }
]";
dynamic obj = new JObject(JArray.Parse(json)
.Select(t => new JProperty((string)t["Name"], t["Value"])));
From there, you can access the properties like you would for an anonymous type (assuming you know what they are):
Console.WriteLine(obj.std_id);
Console.WriteLine(obj.cust_id);
If you don't know what the properties are, you can enumerate them like a dictionary:
foreach (var prop in obj)
{
Console.WriteLine(prop.Name + ": " + prop.Value);
}
Fiddle: https://dotnetfiddle.net/MRY2ny
Why anonymous object? you should deserialize to a type like below
public class RootObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Then what you actually have is IEnumerable<RootObjct>. You can use use Linq and select First() from it like
RootObject = RootObjects.FirstOrDefault()
You could deserialize it into a dynamic. Like this:
var serializer = new JavaScriptSerializer();
var deserializedResult = serializer.Deserialize<dynamic>(json);
Reference:
JavaScriptSerializer Class
var definition = new { Name = "" };
string json1 = #"{'Name':'James'}";
var customer1 = JsonConvert.DeserializeAnonymousType(json1, definition);
Console.WriteLine(customer1.Name);
string json2 = #"{'Name':'Mike'}";
var customer2 = JsonConvert.DeserializeAnonymousType(json2, definition);
Console.WriteLine(customer2.Name);
source http://www.newtonsoft.com/json/help/html/DeserializeAnonymousType.htm
I know that this solution isn't perfect, but it works for the example provided by you and returns result that looks like paramObj in your example.
The idea is to create a custom Json converter.
First, let's create a DTO class to present a name-value item of incomming JSON.
public class NameValueJsonItem
{
public string Name { get; set; }
public string Value { get; set; }
}
Converter implementation:
public class DynamicJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token == null || token.Type != JTokenType.Array)
{
return null;
}
List<NameValueJsonItem> parsedJson = token.ToObject<List<NameValueJsonItem>>();
ExpandoObject result = new ExpandoObject();
foreach (NameValueJsonItem item in parsedJson)
{
if (!String.IsNullOrEmpty(item.Name))
{
(result as IDictionary<string, object>)[item.Name] = item.Value;
}
}
return result;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Sure, you can make it more safe by adding some exceptions handling etc inside the method if you want.
You can use this converter like that:
dynamic result = JsonConvert.DeserializeObject<dynamic>(json, new DynamicJsonConverter());
Hope it will help.

Is there a way to use Json.Net to deserialize some json which calls a method to add the items to a collection (there is no property setter)

I have a class that has a collection which is readonly and has no setter.
Is there a way to de-serialize some json and get it call a method on the instance instead of the property setter?
For example:
public class User
{
private ObservableCollection<Movie> _movies;
public string Name { get; set; }
public ReadOnlyCollection<Movie> FavouriteMovies { get; set; }
public void AddMovie(Movie movie) { .. }
//-or-
public void AddMovies(IEnumerable<Movie> movies){ .. }
}
The only way to get things into the _movies backing field is via the method AddMovies. So when trying to deserialize some valid json which has an array of Movies in the json, it will call AddMovie or AddMovies...
Ninja update by PK:
I've forked the fiddle below using a collection of classes instead of simple strings, for a more complex example that now works, based on the answer below.
Use a JsonConverter to do custom conversion of your json. Here's a simple example of how that might work. Given a class like this:
public class MyClass
{
private List<string> backingField;
public string Name { get; set; }
public IReadOnlyCollection<string> MyStrings { get; private set; }
public MyClass()
{
backingField = new List<string>();
MyStrings = backingField.AsReadOnly();
}
public void AddString(string item)
{
backingField.Add(item);
}
}
And JSON like this:
{
"MyStrings": [
"Foo",
"Bar"
],
"Name":"My stuff"
}
You could create a converter to read that json and call AddString to populate the backingField like this:
public class MyClassConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = new MyClass();
var jObj = JObject.Load(reader);
JsonConvert.PopulateObject(jObj.ToString(), obj); // populate fields we don't need any special handling for
var stringsProp = jObj["MyStrings"];
if (stringsProp != null)
{
var strings = stringsProp.ToObject<List<string>>();
foreach (var s in strings)
{
obj.AddString(s);
}
}
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyClass);
}
}
Now to use it, and keep MyClass ignorant of how it get deserialized, you can simply do something like this:
var m = JsonConvert.DeserializeObject(json, typeof(MyClass), new MyClassConverter()) as MyClass;
Here's a fiddle

Json.NET - Default deserialization behavior for a single property in CustomCreationConverter

In the following scenario, how do I get CrazyItemConverter to carry on as usual when it encounters a JSON property that exists in the type I'm deserializing to?
I have some JSON that looks like this:
{
"Item":{
"Name":"Apple",
"Id":null,
"Size":5,
"Quality":2
}
}
The JSON gets deserialized into a class that looks a whole lot like this:
[JsonConverter(typeof(CrazyItemConverter))]
public class Item
{
[JsonConverter(typeof(CrazyStringConverter))]
public string Name { get; set; }
public Guid? Id { get; set; }
[JsonIgnore]
public Dictionary<string, object> CustomFields
{
get
{
if (_customFields == null)
_customFields = new Dictionary<string, object>();
return _customFields;
}
}
...
}
CrazyItemConverter populates the values of the known properties and puts the unknown properties in CustomFields. The ReadJson in it looks like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var outputObject = Create(objectType);
var objProps = objectType.GetProperties().Select(p => p.Name).ToArray();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
string propertyName = reader.Value.ToString();
if (reader.Read())
{
if (objProps.Contains(propertyName))
{
// No idea :(
// serializer.Populate(reader, outputObject);
}
else
{
outputObject.AddProperty(propertyName, reader.Value);
}
}
}
}
return outputObject;
}
During deserialization, when CrazyItemConverter encounters a known property, I want it to act as it normally would. Meaning, respecting the [JsonConverter(typeof(CrazyStringConverter))] for Name.
I was using the code below to set the known properties but, it throws exceptions on nullables and doesn't respect my other JsonConverters.
PropertyInfo pi = outputObject.GetType().GetProperty(readerValue, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var convertedValue = Convert.ChangeType(reader.Value, pi.PropertyType);
pi.SetValue(outputObject, convertedValue, null);
Any ideas?
UPDATE: I've learned that serializer.Populate(reader, outputObject); is how to deserialize the whole thing but it doesn't seem to work if you want default functionality on a property-by-property basis.
If I'm understanding correctly, your CrazyItemConverter exists so that you can deserialize known properties in the JSON to strongly-typed properties, while still preserving "extra" fields that may be in the JSON into dictionary.
It turns out that Json.Net already has this feature built in (since 5.0 release 5), so you don't need a crazy converter. Instead, you just need to mark your dictionary with the [JsonExtensionData] attribute. (See the author's blog for more information.)
So your Item class would look like this:
public class Item
{
[JsonConverter(typeof(CrazyStringConverter))]
public string Name { get; set; }
public Guid? Id { get; set; }
[JsonExtensionData]
public Dictionary<string, object> CustomFields
{
get
{
if (_customFields == null)
_customFields = new Dictionary<string, object>();
return _customFields;
}
private set
{
_customFields = value;
}
}
private Dictionary<string, object> _customFields;
}
Then you can just deserialize it as normal. Demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Item"":
{
""Name"":""Apple"",
""Id"":""4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac"",
""Size"":5,
""Quality"":2
}
}";
Item item = JsonConvert.DeserializeObject<Wrapper>(json).Item;
Console.WriteLine("Name: " + item.Name);
Console.WriteLine("Id: " + item.Id);
foreach (KeyValuePair<string, object> kvp in item.CustomFields)
{
Console.WriteLine(kvp.Key + ": " + kvp.Value);
}
}
}
public class Wrapper
{
public Item Item { get; set; }
}
class CrazyStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
// Reverse the string just for fun
return new string(token.ToString().Reverse().ToArray());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Output:
Name: elppA
Id: 4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac
Size: 5
Quality: 2

Categories

Resources