TLDR; Can not parse querystring data to object model using Json.net deserializer.
Long version:
I'm using Newtonsoft.Json's DefaultContractResolver for Json serializer in dotnet core 2.0;
var mvc = services.AddMvc();
mvc.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
});
In web api controller, i'm using a model;
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpGet]
public Core.Data.Models.Request.DataSourceRequestModel Get(Core.Data.Models.Request.DataSourceRequestModel req)
{
return req;
}
}
And DataSourceRequestModel is;
public sealed class DataSourceRequestModel
{
[JsonProperty(PropertyName = "start")]
public int Start { get; set; }
[JsonProperty(PropertyName = "limit")]
public int Limit { get; set; }
[JsonProperty(PropertyName = "filter")]
public ICollection<DataSourceRequestFilterModel> Filters { get; set; }
[JsonProperty(PropertyName = "sort")]
public ICollection<DataSourceRequestSortModel> Sorts { get; set; }
public DataSourceRequestModel()
{
Limit = 25;
}
}
public class DataSourceRequestFilterModel
{
[JsonProperty(PropertyName = "property")]
public string Property { get; set; }
[JsonProperty(PropertyName = "operator")]
public string Operator { get; set; }
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
}
public class DataSourceRequestSortModel
{
[JsonProperty(PropertyName = "property")]
public string Property { get; set; }
[JsonProperty(PropertyName = "direction")]
public string Direction { get; set; }
}
I'm calling controller with this query;
http://localhost:9417/api/values/?_dc=1505751693766&page=1&start=99&limit=163&filter=%5B%7B%22Property%22%3A%22Code%22%2C%22Value%22%3A%22%22%2C%22Operator%22%3A%22eq%22%7D%2C%7B%22property%22%3A%22Name%22%2C%22value%22%3A%22%5Cu00fccretli%22%2C%22operator%22%3A%22eq%22%7D%5D
Basically, i'm sending this value as querystring;
_dc:1505755748815
page:1
start:99
limit:163
sort:[{"property":"Name","direction":"ASC"}]
filter:[{"property":"Code","value":"","operator":"eq"},{"property":"Name","value":"Ucretli","operator":"eq"}]
As a result, i'm getting this;
{
"start": 99,
"limit": 163,
"filter": null,
"sort": null
}
Json.net deserializer could not parse filter and sort properties, just simply ignoring them. Even if i change property names, this time it does not return null values but again, i'm getting empty array.
Any ideas which causes that problem?
Thank you in advance.
Edit 1: In DataSourceRequestModel, i've both tried List and DataSourceRequestFilterModel[] array changes and still no success.
Edit 2: For further references, i'm going to share the solution i've found. As dbc2 has mentioned here below, dotnet core does not use Json.Net for complex model binding so the properties in model stays null. So you have to create and use your own custom model binders, detailed here;
http://www.dotnetcurry.com/aspnet-mvc/1368/aspnet-core-mvc-custom-model-binding
Thanks again to everyone who has involved.
Related
I am practicing with web api. My goal is to create a Get endpoint, which receive data from an external api, then return a different result. external api link: https://www.themealdb.com/api/json/v1/1/search.php?f=a, The external api data looks like:
{
"meals": [
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"strDrinkAlternate": null,
"strCategory": "Dessert",
.....
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
....
}
]
}
I want my endpoint provide a different result like the following:
[
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"ingredients": ["Apple", "sugar"...]
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
"ingredients": ["Apple", "sugar"...]
}
]
The following code is what I attempted so far, It's working, but the moment I changed property ingredient1 from public to private, that ingredient in list will become null, also, there are so many ingredients, some of them are null by default, I don't want to add them if they are null, how can I fix these two issues? Thanks a lot
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using RestSharp;
namespace testAPI.Controllers;
public class Content
{
[JsonPropertyName("meals")]
public List<Meal> Meals { get; set; }
}
public class Meal
{
[JsonPropertyName("idMeal")]
public string MealId { get; set; }
[JsonPropertyName("strMeal")]
public string Name { get; set; }
[JsonPropertyName("strIngredient1")]
public string Ingredient1 { get; set; }
[JsonPropertyName("strIngredient2")]
public string Ingredient2 { get; set; }
[JsonPropertyName("strIngredient20")]
public string Ingredient20 { get; set; }
public List<string> Ingredients
{
get { return new List<string>(){Ingredient1, Ingredient2, Ingredient20};}
}
}
[ApiController]
[Route("api/[controller]")]
public class DishesController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
var mealList = JsonSerializer.Deserialize<Content>(response.Content);
return Ok(mealList.Meals);
}
}
To address the problems one at a time...
the moment I changed property ingredient1 from public to private, that ingredient in list will become null
Changing the access modifier affects both deserialization and serialization, so this cannot be used to only stop it from serializing the property. You should split the data models up into what you want to receive and what you want to expose/return.
there are so many ingredients, some of them are null by default, I don't want to add them if they are null
Addition to splitting up the data models you can handle this when mapping from one model to the other.
The following code should fix both issues:
namespace TheMealDb.Models
{
// These are the models you receive from TheMealDb
// JSON converted to classes with https://json2csharp.com/
public class Root
{
public List<Meal> meals { get; set; }
}
public class Meal
{
public string idMeal { get; set; }
public string strMeal { get; set; }
public string strIngredient1 { get; set; }
public string strIngredient2 { get; set; }
public string strIngredient3 { get; set; }
// Other properties removed for brevity...
}
}
namespace Internal.Models
{
// This is the model you want to return from your controller action
public class Meal
{
[JsonPropertyName("id")] // No need to use the same name as from themealdb
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("ingredients")]
public List<string> Ingredients { get; set; }
}
}
Now, to fetch, map and return the data in your controller action:
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
// Deserialize to the "TheMealDb" models
var mealList = JsonSerializer.Deserialize<TheMealDb.Models.Root>(response.Content);
// Map to your own models
var myMealList = mealDbList.meals?.Select(MapToInternal);
return Ok(myMealList);
}
// Map "TheMealDb" model to your own model
private Internal.Models.Meal MapToInternal(TheMealDb.Models.Meal externalMeal)
{
return new Internal.Models.Meal
{
Id = externalMeal.idMeal,
Name = externalMeal.strMeal,
Ingredients = new []
{
externalMeal.strIngredient1,
externalMeal.strIngredient2,
externalMeal.strIngredient3,
// ...
}
// Remove empty/null ingredients
.Where(ingr => !string.IsNullOrEmpty(ingr))
.ToList()
};
}
See the code in action.
I'm developing an api in net core.
I've done a post function in which I send an object containing multiple parameters and a list within another list.
When I'm debugging the code the function is called correctly but I find that the second list always arrives null.
The rest of the data arrives at you correctly. I have done different tests with other objects and everything works correctly.
It is this case in which the list within another the second one arrives null.
My code:
example request input
{
"Name": "TestName",
"Related1":
[{
"id1": "TestNameRelated1",
"Related2":
[{
"id2": "TestNameRelated2"
}]
}]
}
[HttpPost]
public resultExample Test([FromBody]TestClass test)
{
//do something
}
[DataContract]
public class TestClass
{
[DataMember]
public string Name { get; set; }
[DataMember]
public List<TestClassArray> Related1 { get; set; }
}
[DataContract]
public class TestClassArray
{
[DataMember]
public string id1 { get; set; }
[DataMember]
public List<TestClassArray2> Related2 { get; set; }
}
[DataContract]
public class TestClassArray2
{
[DataMember]
public string id2 { get; set; }
}
This api was previously made in .NET framework 4.8 and this case worked correctly.
Now I'm passing the api to .Net5.
Could it be that in .Net5 it is not allowed to pass lists within other lists?
Do you have to enable some kind of configuration to be able to do this now?
You need use class/DTO with constructor like shown below and you should be good to go. I have uploaded this sample API app's code working with .net5.0 on my GitHub here.
public class TestClass
{
public TestClass()
{
Related1 = new List<TestClassArray>();
}
public string Name { get; set; }
public List<TestClassArray> Related1 { get; set; }
}
public class TestClassArray
{
public TestClassArray()
{
Related2 = new List<TestClassArray2>();
}
public string id1 { get; set; }
public List<TestClassArray2> Related2 { get; set; }
}
public class TestClassArray2
{
public string id2 { get; set; }
}
public class ResultExample
{
public string StatusCode { get; set; }
public string Message { get; set; }
}
Controller Post Method
[HttpPost]
[ProducesResponseType(typeof(ResultExample), 200)]
public ResultExample Post([FromBody] TestClass test)
{
ResultExample testResult = new ResultExample();
TestClass test2 = new TestClass();
TestClassArray testClassArray = new TestClassArray();
TestClassArray2 testClassArray2 = new TestClassArray2();
test2.Name = test.Name;
foreach (var item in test.Related1)
{
foreach (var item2 in item.Related2)
{
testClassArray2.id2 = item2.id2;
}
testClassArray.Related2.Add(testClassArray2);
}
test2.Related1.Add(testClassArray);
Console.WriteLine(test2);
testResult.Message = "New Result added successfullly....";
testResult.StatusCode = "201";
return testResult;
}
Swagger Input Sample Payload
Post Controller Result
Response of Sample input payload,(You can change it to default 201 response code as well)
I had a similar issue.
API method shows List was null
In my case a date field was not well formatted
So I use SimpleDateFormat on Android Studio with a correct datetime format
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",Locale.US);
item.setDate(dateFormat.format(calendar.getTime()));
and works fine
I have following Json-based configuration file:
{
"PostProcessing": {
"ValidationHandlerConfiguration": {
"MinimumTrustLevel": 80,
"MinimumMatchingTrustLevel": 75
},
"MatchingCharacterRemovals": [
"-",
"''",
":"
]
},
"Processing": {
"OrderSelection": {
"SelectionDaysInterval": 30,
"SelectionDaysMaximum": 365
}
}
}
As serialization framework I use Newtonsoft. To serialize this config into objects I have implemented following classes:
[JsonObject(MemberSerialization.OptIn)]
public class RecognitionConfiguration {
[JsonProperty(PropertyName = "PostProcessing", Required = Required.Always)]
public PostRecognitionConfiguration PostRecognitionConfiguration { get; set; }
[JsonProperty(PropertyName = "Processing", Required = Required.Always)]
public ProcessRecognitionConfiguration ProcessRecognitionConfiguration { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class PostRecognitionConfiguration {
[JsonProperty(Required = Required.Always)]
public ValidationHandlerConfiguration ValidationHandlerConfiguration { get; set; }
[JsonProperty] public List<string> MatchingCharacterRemovals { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class ProcessRecognitionConfiguration {
[JsonProperty(PropertyName = "OrderSelection", Required = Required.Always)]
public OrderSelectionConfiguration OrderSelectionConfiguration { get; set; }
}
In a class I try to serialize a specific configuration section into these class structures using IConfigurationSection.Get().
var serializedConfiguration = this.ConfigurationSection.Get<RecognitionConfiguration>();
But when I debug the code, I always get an "empty" variable serializedConfiguration which is not null, but all properties are null.
If I use
this.ConfigurationSection.GetSection("Processing").Get<ProcessRecognitionConfiguration>()
or change the naming of the properties in the json file to exactly match the property names in the classes like this:
{
"ProcessRecognitionConfiguration": {
"OrderSelectionConfiguration": {
"SelectionDaysInterval": 30,
"SelectionDaysMaximum": 365
}
}
}
it it works fine. Do you have any idea, why setting PropertyName on JsonProperty does not seem to have any effect?
That is by design. Binding to POCO via configuration is done by convention. Not like Model Binding to Controller Action parameters.
It matches property names on the POCO to keys in the provided JSON.
Reference Configuration in ASP.NET Core
So either you change the settings to match the class like you showed in the original question, or change the class to match the settings keys in the Json-based configuration file.
[JsonObject(MemberSerialization.OptIn)]
public class RecognitionConfiguration {
[JsonProperty(PropertyName = "PostProcessing", Required = Required.Always)]
public PostRecognitionConfiguration PostProcessing{ get; set; }
[JsonProperty(PropertyName = "Processing", Required = Required.Always)]
public ProcessRecognitionConfiguration Processing{ get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class PostRecognitionConfiguration {
[JsonProperty(Required = Required.Always)]
public ValidationHandlerConfiguration ValidationHandlerConfiguration { get; set; }
[JsonProperty]
public List<string> MatchingCharacterRemovals { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class ProcessRecognitionConfiguration {
[JsonProperty(PropertyName = "OrderSelection", Required = Required.Always)]
public OrderSelectionConfiguration OrderSelection { get; set; }
}
public partial class ValidationHandlerConfiguration {
[JsonProperty("MinimumTrustLevel")]
public long MinimumTrustLevel { get; set; }
[JsonProperty("MinimumMatchingTrustLevel")]
public long MinimumMatchingTrustLevel { get; set; }
}
public partial class OrderSelectionConfiguration {
[JsonProperty("SelectionDaysInterval")]
public long SelectionDaysInterval { get; set; }
[JsonProperty("SelectionDaysMaximum")]
public long SelectionDaysMaximum { get; set; }
}
EDIT: I found this one is much more pleasant than my previous solutions: Bind everything in an ExpandoObject, write them to JSON and use JSON.NET to bind them back. Using the code of this article:
namespace Microsoft.Extensions.Configuration
{
public static class ConfigurationBinder
{
public static void BindJsonNet(this IConfiguration config, object instance)
{
var obj = BindToExpandoObject(config);
var jsonText = JsonConvert.SerializeObject(obj);
JsonConvert.PopulateObject(jsonText, instance);
}
private static ExpandoObject BindToExpandoObject(IConfiguration config)
{
var result = new ExpandoObject();
// retrieve all keys from your settings
var configs = config.AsEnumerable();
foreach (var kvp in configs)
{
var parent = result as IDictionary<string, object>;
var path = kvp.Key.Split(':');
// create or retrieve the hierarchy (keep last path item for later)
var i = 0;
for (i = 0; i < path.Length - 1; i++)
{
if (!parent.ContainsKey(path[i]))
{
parent.Add(path[i], new ExpandoObject());
}
parent = parent[path[i]] as IDictionary<string, object>;
}
if (kvp.Value == null)
continue;
// add the value to the parent
// note: in case of an array, key will be an integer and will be dealt with later
var key = path[i];
parent.Add(key, kvp.Value);
}
// at this stage, all arrays are seen as dictionaries with integer keys
ReplaceWithArray(null, null, result);
return result;
}
private static void ReplaceWithArray(ExpandoObject parent, string key, ExpandoObject input)
{
if (input == null)
return;
var dict = input as IDictionary<string, object>;
var keys = dict.Keys.ToArray();
// it's an array if all keys are integers
if (keys.All(k => int.TryParse(k, out var dummy)))
{
var array = new object[keys.Length];
foreach (var kvp in dict)
{
array[int.Parse(kvp.Key)] = kvp.Value;
}
var parentDict = parent as IDictionary<string, object>;
parentDict.Remove(key);
parentDict.Add(key, array);
}
else
{
foreach (var childKey in dict.Keys.ToList())
{
ReplaceWithArray(input, childKey, dict[childKey] as ExpandoObject);
}
}
}
}
}
Usage:
var settings = new MySettings();
this.Configuration.BindJsonNet(settings);
Here is my testing MySettings class:
public class MySettings
{
[JsonProperty("PostProcessing")]
public SomeNameElseSettings SomenameElse { get; set; }
public class SomeNameElseSettings
{
[JsonProperty("ValidationHandlerConfiguration")]
public ValidationHandlerConfigurationSettings WhateverNameYouWant { get; set; }
public class ValidationHandlerConfigurationSettings
{
[JsonProperty("MinimumTrustLevel")]
public int MinimumTrustLevelFoo { get; set; }
[JsonProperty("MinimumMatchingTrustLevel")]
public int MinimumMatchingTrustLevelBar { get; set; }
}
}
}
After the calling, I get everything as you desired:
Old Answer:
According to the source code here, it is simply (near) impossible to do what you are requiring. I have tried both JsonProperty and DataContract, none of which are honored by the Binder, simply because the source code itself simply use the property name.
If you still insist, there are 2 possibilities, however I do not recommend any as changing properties' names are much simpler:
Fork your source code there, or simply copy that file (in my attempt to trace the code, I rename all methods to something like Bind2, BindInstance2 etc), and rewrite the code accordingly.
This one is very specific to current implementation, so it's not future-proof: the current code is calling config.GetSection(property.Name), so you can write your own IConfiguration and provide your own name for GetSection method and tap it into the bootstrap process instead of using the default one.
Changing PropertyName on JsonProperty does have effect. Here is the same I tried and it did worked for me:
my JSON data:
{"name": "John","age": 30,"cars": [ "Ford", "BMW", "Fiat" ]}
and the Model:
public class RootObject
{
[JsonProperty(PropertyName ="name")]
public string Apple { get; set; }
public int age { get; set; }
public List<string> cars { get; set; }
}
and here is the code:
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
and this is the output i get
You need to set the PropertyName in JsonProperty same as json file property name but your C# model property can be what you wanted, just that they need to be decorated with [JsonProperty(PropertyName ="jsonPropertyName")] Hope this helps you solve your issue.
Happy coding...
I am trying to do the following inside a Dictionary<string,string>
{
"name": "Bob Barker",
"devName": "InformationServices",
"ReturnedData": [{
"level_heading": "blah1",
"DeliverBestMedicalValue": "blah2",
"level_question": "blah3"
}]
}
I can add the name and devName just fine but I am unsure on how to go about adding the ReturnedData part of the array to the list so that it will return as the layout above?
Example code I am using:
febRecords.RootObject febRecordsData = JsonConvert.DeserializeObject<febRecords.RootObject>(serverResponse);
Dictionary<string,string> febFormData = new Dictionary<string,string>();
febFormData.Add("name", data.firstname.ToString());
febFormData.Add("devName", febData["Data"]["DevisionName"].ToString());
febFormData.Add("ReturnedData", ???); //<-this is where I am stuck
return Ok(JsonConvert.SerializeObject(febFormData, Newtonsoft.Json.Formatting.Indented));
As you see, febFormData.Add("ReturnedData", ???); is the spot where I am stuck and dont really know what to do in order to get the dictionary to output the correct JSON format like I want.
Any help would be great!
update
Would this be how the class needs to look?
public class theOutput
{
public string name { get; set; }
public string devName { get; set; }
public List<string> ReturnedData { get; set; }
}
As suggested by #Panagiotis Kanavos you'd really be better off having .NET entity to match your JSON data. For example:
// propertyname attributes can be ignored if property names
// match the json data property names 1:1
[JsonObject]
public class MyClass
{
public MyClass()
{
ReturnedData = new List<ReturnedData>();
}
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "devName")]
public string DevName { get; set; }
[JsonProperty(PropertyName = "ReturnedData")]
public List<ReturnedData> ReturnedData { get; set; }
}
[JsonObject]
public class ReturnedData
{
[JsonProperty(PropertyName = "level_heading")]
public string LevelHeading { get; set; }
[JsonProperty(PropertyName = "DeliverBestMedicalValue")]
public string DeliverBestMedicalValue { get; set; }
[JsonProperty(PropertyName = "level_question")]
public string LevelQuestion { get; set; }
}
This would make conversion to/from JSON that much easier.
[TestMethod]
public void TestSome()
{
string json = #"{
""name"": ""Bob Barker"",
""devName"": ""InformationServices"",
""ReturnedData"": [{
""level_heading"": ""blah1"",
""DeliverBestMedicalValue"": ""blah2"",
""level_question"": ""blah3""
}]
}";
var obj = JsonConvert.DeserializeObject<MyClass>(json);
Assert.IsTrue(JToken.DeepEquals(JObject.Parse(json), JObject.FromObject(obj)));
}
You can always generate stubs from you JSON data using e.g. http://json2csharp.com/
Here "live" .NET fiddle, too https://dotnetfiddle.net/9ACddp
I have seen some other questions like this, but those are quite complex JSON data's that have objects within objects. Although the JSON I'm working with is never static, I doubt it's as complex as those. Also, it's my first time using JSON with C# so I'm a little clueless.
What I'm trying to achieve is to separate the data that is received from an API that I prompt using WebRequest in C#.
{
"johhny.debt": {
"id":35187540,
"name":"johnny.debt",
"profileIconId":786,
"Level":30,
"revisionDate":1428019045000
}
}
The returned JSON data is in a fashion like thereof.
I want to be able to access all of the properties of the above string in the following manner:
ID :
Name:
~~
~~
~~
... and so forth.
I'm assuming some type of class has to be made for this?
All help is appreciated, thank you all in advance.
Install Json.Net from Nuget
Install-Package Newtonsoft.Json
https://www.nuget.org/packages/Newtonsoft.Json/
Declare class for inner object ({"id":..., "name": ... }):
public class InnerObject
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Username { get; set; }
[JsonProperty("profileIconId")]
public int ProfileIconId { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("revisionDate")]
public string RevisionDate { get; set; }
}
As you can see you can specify rename mapping from json fields to .Net object properties using JsonPropertyAttribute.
Read your json to Dictionary<string,InnerObject> and get value of "johhny.debt" key:
var dict = JsonConvert.DeserializeObject<Dictionary<string, InnerObject>>(jsonText);
var johhny = dict["johhny.debt"];
Or if your need always to parse exact json property 'johhny.debt', you could create root object class:
public class RootObject
{
[JsonProperty("johhny.debt")]
public InnerObject JohhnyDept { get; set; }
}
And deserialize it:
var root = JsonConvert.DeserializeObject<RootObject>(jsonText);
var johhny = root.JohhnyDebt;
Just Create a class like this
public class RootObject
{
public int Id { get; set; }
public string name { get; set; }
public int profileIconId { get; set; }
public int Level { get; set; }
public string revisionDate { get; set; }
}
then install json.Net and this code to your main method
var jsonObject=JsonConvert.DeserializeObject<RootObject>(jsonText);
That's all
Update
var obj = JObject.Parse(json);
var RootObject = new RootObject()
{
Id = (int)obj["johhny.debt"]["id"],
Level = (int)obj["johhny.debt"]["Level"],
name = (string)obj["johhny.debt"]["name"],
profileIconId = (int)obj["johhny.debt"]["profileIconId"],
revisionDate = (string)obj["johhny.debt"]["revisionDate"]
};