I need to deserialize some JSON into my object where the casing of the JSON is unknown/inconsistent. JSON.NET is supposed to be case insensitive but it not working for me.
My class definition:
public class MyRootNode
{
public string Action {get;set;}
public MyData Data {get;set;}
}
public class MyData
{
public string Name {get;set;}
}
The JSON I receive has Action & Data in lowercase and has the correct casing for MyRootNode.
I'm using this to deserialize:
MyRootNode ResponseObject = JsonConvert.DeserializeObject<MyRootnode>(JsonString);
It returns to be an initialised MyRootNode but the Action and Data properties are null.
Any ideas?
EDIT: Added JSON
{
"MyRootNode":{
"action":"PACT",
"myData":{
"name":"jimmy"
}
}
}
This is the .NET Core built-in JSON library.
I found another way of doing it.. just in case, somebody is still looking for a cleaner way of doing it. Assume there exists a Movie class
using System.Text.Json;
.
.
.
var movies = await JsonSerializer.DeserializeAsync
<IEnumerable<Movie>>(responseStream,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Startup Options:
You can also configure at the time of application startup using the below extension method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddJsonOptions(
x =>
{
x.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
}
Simply add JsonProperty attribute and set jsonProperty name
public class MyRootNode
{
[JsonProperty(PropertyName = "action")]
public string Action {get;set;}
[JsonProperty(PropertyName = "myData")]
public MyData Data {get;set;}
}
public class MyData
{
[JsonProperty(PropertyName = "name")]
public string Name {get;set;}
}
UPD: and yes, add some base type as #mjwills suggest
You need to add an additional class:
public class MyRootNodeWrapper
{
public MyRootNode MyRootNode {get;set;}
}
and then use:
MyRootNodeWrapperResponseObject = JsonConvert.DeserializeObject<MyRootNodeWrapper>(JsonString);
https://stackoverflow.com/a/45384366/34092 may be worth a read. It is basically the same scenario.
Also, change:
public MyData Data {get;set;}
to:
public MyData MyData {get;set;}
as per advice from #demo and #Guy .
Related
I have a DTO that has a property that is relevant only to the GET response.
In the POST request I don't need it - more than this - I should not include it in the serialized json.
So, I get this DTO as a response for the GET request and send it (without the mentioned property) in the Body when making a POST request.
Except for removing the optional property from the original class and locate it in a an inherited class - what other options do I have ? (using the JsonProperties, for example or other options)
This is my DTO:
public class MyDTO
{
[JsonProperty("id")]
public int ID {get; set;}
[JsonProperty("remark")]
public string Remark {get; set;}
[JsonProperty("optionalremark")] //relevant only to the GET request
public string OptionalRemark {get; set;}
}
There are several ways how you achieve that:
Separate object
The most obvious choice could be to separate GET's response object from POST's request object. Most probably the two API can evolve independently. If any of these two changes then it requires modification on the DTO. You can use some sort of mapper (like AutoMapper) to define relationship between these two classes.
Conditional Serialization
Newtonsoft does support conditional seralization. All you have to do is to define a method which returns a bool and is named like ShouldSerialize{PropertyName}:
public class MyDTO
{
[JsonProperty("id")]
public int ID {get; set;}
[JsonProperty("remark")]
public string Remark {get; set;}
[JsonProperty("optionalremark")]
public string OptionalRemark {get; set;}
public bool ShouldSerializeOptionalRemark() => false;
}
This method is called only during serialization. So, deserialization works as expected.
ContractResolver
If you don't want to include a ShouldSerialize{PropertyName} method into your DTO then you can place this logic inside a custom ContractResolver:
public class MyDTOContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType == typeof(MyDTO) && property.PropertyName == nameof(MyDTO.OptionalRemark).ToLower())
{
property.ShouldSerialize = _ => false;
}
return property;
}
}
You can specify this resolver for SerializeObject and DeserializeObject as well:
var getResponse = JsonConvert.DeserializeObject<MyDTO>(json, new JsonSerializerSettings { ContractResolver = new MyDTOContractResolver() });
var postRequest = JsonConvert.SerializeObject(getResponse, new JsonSerializerSettings { ContractResolver = new MyDTOContractResolver() });
During deserialization it does not have any affect. So, if you need to you can register this resolver globally as well:
services.AddControllers()
.AddNewtonsoftJson(opts =>
{
opts.SerializerSettings.ContractResolver = new MyDTOContractResolver();
});
This code works in ASP.NET Core 3.x or higher projects. In case of older ASP.NET Core project, please use AddJsonOptions instead of AddNewtonsoftJson.
In this case, You can use [JsonIgnore] attribute on you DTO property, like this:-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<SomethingDTO> something_dto { get; set; }
I am using following code in ASP.NET Web API application.
//Support camel casing
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().FirstOrDefault();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
When returning JSON via POCO or DataTable, it converts property name in camel casing.
Assume My class has two properties.
Class Obj{
public string DataBase{ get; set; }
public string ChangedBy { get; set; }
}
When I return any object of this class, I will get JSON like this:
{
"dataBase":"Oracle",
"changedBy":"XYZ"
}
It seems issue is when you have '_' in the property name. CamelCasing is not making sense.
My class has columns like this:
DATA_BASE
CHANGED_BY
Now, I am receiving JSON like this:
{
"datA_BASE":"Oracle",
"changeD_BY":"XYZ"
}
I was expecting:
{
"dATA_BASE":"Oracle",
"cHANGED_BY":"XYZ"
}
I would like to enhance final result that ModelBinder returns.
For example:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
public string Title {get;set;}
}
In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.
For example:
[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
model.Title should be upper case, even if send from client in lower case.
}
My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.
What would be the best option to achieve desired behaviour?
Important: (edited):
It would be nice if directive attributes could be stacked:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
[RemoveSpacesAttribute]
public string Title {get;set;}
}
Edited:
It looks similar to this, but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.
I should say, that it would be nice if JSON case rule would work:
public class MyModel
{
public int Order {get;set;}
public string Title {get;set;}
}
should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.
This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.
public class MyModel
{
private string _title;
public int Order {get;set;}
public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}
There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters, not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter.
First the attribute:
[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}
Then we decorate our model class with it:
public class MyModel
{
public int Order { get; set; }
[ToUppercase]
public string Title { get; set; }
}
Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:
public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
private readonly JsonInputFormatter _jsonInputFormatter;
public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
{
_jsonInputFormatter = jsonInputFormatter;
foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
SupportedEncodings.Add(supportedEncoding);
foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
SupportedMediaTypes.Add(supportedMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);
foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
&& p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
{
var value = (string)property.GetValue(result.Result.Model);
property.SetValue(result.Result.Model, value.ToUpper());
}
return result;
}
}
Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:
public static class MvcOptionsExtensions
{
public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
{
if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
{
var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
}
}
}
And finally, call that method to effect the replacement in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options => options.UseToUppercaseJsonInputFormatter());
}
}
Et voilĂ !
You can do this thing inside your MyUpperCaseAttribute as follows:
public class MyUpperCaseAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
validationContext.ObjectType
.GetProperty(validationContext.MemberName)
.SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
}
return null;
}
}
Property value will be converted to UpperCase during Model Binding. I have checked it in my side and it works perfectly.
The system i am developing is using DataContractJsonSerializer.
The service looks like this:
[HttpPost, Route("RunQuery")]
public List<BIResultRecord> RunQuery(BIQuery query) {
// Logic
}
The BIQuery class hierarchy is as follows:
[DataContract]
[KnownType(typeof(BIQuery1))]
public class BIQuery
{
[DataMember]
public string Member { get; set; }
[DataMember]
public QueryTypeEnum QueryType { get; set; }
}
[DataContract]
public class BIQuery1 : BIQuery
{
public BIQuery1()
{
QueryType = QueryTypeEnum.Type1;
}
[DataMember]
public ClassSpecificObject Object { get; set; }
}
THE PROBLEM:
Although i am sending BIQuery1 as a json object it is always deserialized (in my RunQuery method) as the parent object (BIQuery).
WHAT I'VE TRIED:
I've removed the default formatter and added a new one:
public class DataContractJsonFormatter : JsonMediaTypeFormatter
{
public override DataContractJsonSerializer CreateDataContractSerializer(Type type)
{
return new DataContractJsonSerializer(type, new DataContractJsonSerializerSettings() { EmitTypeInformation = EmitTypeInformation.AsNeeded });
}
}
I serialized the object in .NET to see the JSON structure and
cloning it to the request. I saw that it adds
"__type" : "BIQuery1"
to the JSON, which i'm using as well and it changed nothing.
Please help!
MORE DETAILS
The .NET system (client and server) runs in production as is, so only minor changes currently allowed. I'm making also a web client that would work with the existing services over REST.
[HttpPost, Route("RunQuery")]
public List<BIResultRecord> RunQuery(BIQuery query) {
// Logic
}
Your argument in the RunQuery method is the of type BIQuery object. It is deserializing as a BIQuery object because that is the type you are telling the method it will be passed. If you are passing an object of the type BIQuery1 to the method, do this instead:
[HttpPost, Route("RunQuery")]
public List<BIResultRecord> RunQuery(BIQuery1 query) {
// Logic
}
In Swagger UI I get a model like:
Inline Model [
Inline Model 1
]
Inline Model 1 {
Id (string, optional),
ConnectionString (string, optional),
ConnectionState (string, optional)
}
for a REST Get method like:
public IEnumerable<Device> Get()
{
return new List<Device>();
}
Why is it not displayed correctly?
Adding Swagger Config from comments
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration .EnableSwagger(c => { c.SingleApiVersion("v1", "api"); }) .EnableSwaggerUi(c => { });
}
}
public class Device
{
public string Id { get; set; }
public string ConnectionString { get; set; }
public string ConnectionState { get; set; }
}
In C# Asp.Net web api, I did this:
1- In SwaggerConfig.cs
.EnableSwagger(c =>
{//add this line
c.SchemaFilter<ApplyModelNameFilter>();
}
2- add a class that implements ISchemaFilter:
class ApplyModelNameFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
schema.title = type.Name;
}
}
I got the idea from here
It seems like Swagger and/or NSwag do not handle Generic List/IList/IEnumerable types very well as a base type, perhaps because some frameworks that may try to connect with Swagger don't understand them.
I have worked around this by wrapping my List in another object. So, in your case, you may need to do something like:
public ListResponseObject<T>()
{
public IEnumerable<T> ResponseList {get; set;}
}
And then return from your controller like this:
public ListResponseObject<Device> Get()
{
return new ListResponseObject<Device>{ResponseList = new List<Device>()};
}
Not as simple... but should get it through Swagger better.
We've leveraged this to our advantage. We've applied this technique to all controllers (or something similar) so we have a more standardized response. We can also do this:
public ListResponseObject<T>() : ResponseObject<T>
{
public IEnumerable<T> ResponseList {get; set;}
}
public ResponseObject<T>()
{
public string Message {get; set;}
public string Status {get; set;}
}
And now you have a container that will make downstream handling a little easier.
Not an exact answer, but a work-around that's worked for us. YMMV
UPDATE: Here's a response to a question I posted in the NSwag GitHub issues:
I think its correct as is. Currently swagger and json schema do not support generics (only for arrays) and thus all generic types are expanded to non-generic/specific types... altough the models should be correct but you may end up with lots of classes...
An enhancement for supporting generics is planned but this will be not compliant with swagger and only work with nswag... (No support in swagger ui)