I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.
Related
I'm developing an ASP.NET Core web API, without EntityFrameworkCore, that uses controller classes for implementing the HTTP actions, and that generally follows this pattern:
public class MyItem
{
public string Prop1 { get; set; }
public MyOtherItem? OtherItem { get; set; }
}
public IEnumerable<MyItem> GetMyItems()
{
List<MyItem> myItems = new();
// Fill in myItems with stuff where OtherItem is sometimes null
return myItems;
}
public class MyController : ControllerBase
{
[HttpPost]
public Task<IEnumerable<MyItem>> FetchMyItems()
{
var myItems = GetMyItems();
return Task.FromResult(myItems);
}
}
On making the POST request to FetchMyItems in Postman, the response is a JSON string containing an array of MyItem objects like this:
[
{
"prop1": "a string",
"otherItem": {
"otherprop1": "a string",
"otherprop2": 0
}
},
{
"prop1": "a string",
"otherItem": null
}
]
What I would like is for the OtherItem property to be excluded if it is null. I have looked at adding the [JsonProperty(NullValueHandling=NullValueHandling.Ignore)] attribute to the class but that has no effect, probably because whatever process is happening in FromResult() is not using the JsonSerializer.
Is there a different property attribute I can use? Or do I have to alter my action to use JsonSerializer?
You can add the ignore null property condition when adding the controller. This uses System.Text.Json and not Newtonsoft library.
For .NET 5 and above:
builder.Services.AddControllers()
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
});
For .NET Core 3.1:
services.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreNullValues = true;
});
You can use like this:
string json = JsonConvert.SerializeObject(myItems, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Below is a (slightly) stripped down response I get from a REST API upon successful creation of a new "job code" entry. I need to deserialize the response into some classes, but I'm stumped.
For reference, I'm using JSON.NET in .NET 3.5 (running in a SSIS script in SQL Server 2008 R2) to attempt my deserialization. Here's the JSON - which I obviously have no control over as it's coming from someone else's API:
{
"results":{
"jobcodes":{
"1":{
"_status_code":200,
"_status_message":"Created",
"id":444444444,
"assigned_to_all":false,
"billable":true,
"active":true,
"type":"regular",
"name":"1234 Main Street - Jackson"
},
"2":{
"_status_code":200,
"_status_message":"Created",
"id":1234567890,
"assigned_to_all":false,
"billable":true,
"active":true,
"type":"regular",
"name":"4321 Some Other Street - Jackson"
}
}
}
}
In my C# code, I do have a "JobCode" class defined which only partially maps the JSON values to properties - I'm not interested in all of the data that's returned to me:
[JsonObject]
class JobCode
{
[JsonProperty("_status_code")]
public string StatusCode { get; set; }
[JsonProperty("_status_message")]
public string StatusMessage { get; set; }
[JsonProperty("id")]
public string Id {get; set;}
[JsonProperty("name")]
public string Name { get; set; }
//-------------------------------------------------------------------------------
// Empty constructor for JSON serialization support
//-------------------------------------------------------------------------------
public JobCode() { }
}
I'm attempting to deserialize the data via this call:
newResource = JsonConvert.DeserializeObject<JobCode>(jsonResponse);
Where jsonResponse is the code outputted above.
When I execute the code, "newResource" always comes back as null - which is not unexpected because I know that there are actually multiple jobcodes in the data and this code is trying to deserialize it into a single JobCode object. I tried creating a new class called "JobCodes" that looks like this:
class JobCodes
{
[JsonProperty("jobcodes")]
public List<JobCode>_JobCodes { get; set; }
}
And then I tried calling this:
newResource = JsonConvert.DeserializeObject<JobCodes>(jsonResponse);
But the issue persists - my return object is null.
What's throwing me off, I think, is the presence of the "1" and "2" identifiers. I don't know how to account for their presence in my object design and/or usage of the JSON.NET class / property attributes like [JsonObject],[JsonProperty], etc.
When I run the JSON data through JSON2CSharp, it constructs some weird-looking classes, so that hasn't proven too effective. I've validated the JSON with several different validators and it all checks out - I just don't know what I'm missing here.
Ultimately, I'd like to return a List from the JSON data, but I'm stumped on what I need to do to make that happen.
Your problem is twofold:
You don't have a class defined at the root level. The class structure needs to match the entire JSON, you can't just deserialize from the middle.
Whenever you have an object whose keys can change, you need to use a Dictionary<string, T>. A regular class won't work for that; neither will a List<T>.
Make your classes like this:
class RootObject
{
[JsonProperty("results")]
public Results Results { get; set; }
}
class Results
{
[JsonProperty("jobcodes")]
public Dictionary<string, JobCode> JobCodes { get; set; }
}
class JobCode
{
[JsonProperty("_status_code")]
public string StatusCode { get; set; }
[JsonProperty("_status_message")]
public string StatusMessage { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
Then, deserialize like this:
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
Working demo here
Excellent Answers!
For those out there that may need some more help with the JSON Class Configuration, try: http://json2csharp.com/#
An excellent way of Auto Generating the Classes!
Or even easier, in VS, Goto:
Edit -> Paste Special -> Paste as JSON Classes
Because you can't change the scheme of JSON, and you can't set constant No. of properties, I'd suggest you to use JObject
var jobject = JObject.Parse(json);
var results = jobject["results"];
var jobcodes = results["jobcodes"];
var output = jobcodes.Children<JProperty>()
.Select(prop => prop.Value.ToObject<JobCode>())
.ToList();
Warning: code assumes, that JSON is always in proper schema. You should also handle invalid schema (for example where property is not of JobCode scheme).
You can also deserialize your json to an object of your target class, and then read its properties as per normal:
var obj = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
Console.WriteLine($"Property: {obj.Property}");
where DeSerializeFromStrToObj is a custom class that makes use of reflection to instantiate an object of a targeted class:
public static T DeSerializeFromStrToObj<T>(string json)
{
try
{
var o = (T)Activator.CreateInstance(typeof(T));
try
{
var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
var props = o.GetType().GetProperties();
if (props == null || props.Length == 0)
{
Debug.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
return default;
}
if (jsonDict.Count != props.Length)
{
Debug.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
return default;
}
foreach (var prop in props)
{
if (prop == null)
{
Debug.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
return default;
}
if (!jsonDict.ContainsKey(prop.Name))
{
Debug.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
return default;
}
var value = jsonDict[prop.Name];
Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
object safeValue = value ?? Convert.ChangeType(value, t);
prop.SetValue(o, safeValue, null); // initialize property
}
return o;
}
catch (Exception e2)
{
Debug.WriteLine(e2.Message);
return o;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return default;
}
}
A complete working example class can be found in my enhanced answer to a similar question, here
I have to manage a lot of API calls to a service and all the response messages have a common structure except the "data" field which varies according to the endpoint called and whether the call was successful or not.
Looking for a way to smartly manage the whole situation I produced a satisfying solution with the use of generics and Mapster.
This is a typical response message:
{
"type": "send",
"datetime": "2022-02-21",
"correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
"data": {
"id": 22,
"description": "blue t-shirt with stripes",
"category": "t-shirt",
"size": "XL"
}
}
The data field is totally variable, sometimes it is a one-level structure, sometimes a multi-level structure, sometimes an array, sometimes a simple string and sometimes a null.
Clearly the response messages are known and depends on the called endpoint so I know what to expect when I make the call but there is a case where the structure can still change.
If the call is unsuccessful and there is an error, a 200 is still returned from the endpoint but the response is like this:
{
"type": "error",
"datetime": "2022-02-21",
"correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
"data": {
"id": 1522,
"description": "product code not found",
}
}
Looking for an elegant and concise solution to manage all cases with a single method, I was able to find a solution:
These are my models:
public class Response<T> where T : class
{
public string type { get; set; }
public T data { get; set; } = null;
public Error Error { get; set; } = null;
public bool IsError => Error != null;
public string ErrorMessage => IsError ? $"An error occurred. Error code {Error.id} - {Error.description}" : "";
}
public class Customer
{
public int customerId { get; set; }
public string name { get; set; }
}
public class Supplier
{
public int supplierId { get; set; }
public string company { get; set; }
}
public class Error
{
public int id { get; set; }
public string description { get; set; }
}
And this is my function that manage all the deserializations:
private static Response<T> GetData<T>(string json) where T : class
{
//Deserialize the json using dynamic as T so can receive any kind of data structure
var resp = JsonConvert.DeserializeObject<Response<dynamic>>(json);
var ret = resp.Adapt<Response<T>>();
if (resp.type == "error")
{
//Adapt the dynamic to Error property
ret.Error = ((object)resp.data).Adapt<Error>();
ret.data = null;
}
return ret;
}
So I call my function in this way:
var customerData = GetData<Customer>("{\"type\":\"send\", \"data\": {\"id\":1, \"name\": \"John Ross\"}}");
if (customerData.IsError)
Console.WriteLine($"ERROR! {customerData.ErrorMessage}");
else
Console.WriteLine($"The response is OK. Customer name is {customerData.data.name}");
As you can see, the solution adopted is elegant and works very well.
The only problem I haven't found solution is that Mapster.Adapt doesn't fail if I try to fit the wrong json to type T.
So if I deserialize the json of the customer in the supplier class I would not notice the problem.
Is there a way in Mapster to know if the object I'm trying to adapt isn't compatible with the target type?
So I could raise an exception and my procedure would be perfect.
Here is a repo with working example
https://github.com/mmassari/MapDynamicWIthMapster
Thank you
The solution you are trying to work out might seem elegant, but in reality you could be introducing a code smell which might create hard to catch bugs in the future.
You might want to reconsider using the knowledge you already have about the endpoints to deserialize the response to their correct response types. This is good practice, and it is simple to understand and maintain for both yourself and future developers.
With regards to the problematic "product code not found" responses, you could add a validation step that deserializes the JSON response to an object of type JObject which you can safely query to check for existence of known error messages.
Here is a simple example:
private static void ValidateResponse(string json)
{
var knownErrors = new List<string>()
{
"Product code not found",
"Customer does not exist",
"Other known errors here"
};
var jobj = JsonConvert.DeserializeObject<JObject>(json);
var error = jobj.SelectToken("data.description");
if (error != null)
{
var errorMessage = error.Value<string>();
if (knownErrors.Contains(errorMessage))
{
throw new ApplicationException(errorMessage);
}
}
}
You may want to consider trimming and normalizing casing to avoid getting thrown off by small variations in the error messages.
I'm trying to construct a request body for a REST api call, and I need to create a JSON object with the list of properties I want to get back.
For eg: I have this C# object that I want to get back:
public class SomeProperties
{
public string TicketNumber { get; set; }
public Driver Driver { get; set; }
}
public class Driver
{
public string Name { get; set; }
}
To get this back, I need to put these properties in a JSON request body like this:
"properties": [
"ticketNumber",
"driver.name"
]
My attempt looks like this:
private string FetchProperties()
{
var fetchProperties = new
{
properties = new List<string>
{
"ticketNumber",
"driver.name"
}
};
var jsonResult = JsonConvert.SerializeObject(fetchProperties, Formatting.None);
return jsonResult;
}
But I don't want to hard code the properties like that.
So is there any way I can use property names from the object I want, to put in the list of strings that I made in the method above?
Thank You!
If I understand correctly,you need Metadata of model.
if you use EntityFramework, you can get metadata of your model
from this Code
and call BuildJsonMetadata() function
and if you use other mapper, I dont see any exist tool for generate metadata of model and you must generate it handly
somthing like this
First of, if you serialize the class you have (SomeProperties), you will not get driver.name. Instead you will get a string like this one that shows driver as an object,
{
properties : {
"ticketNumber" : "stringvalue",
"driver" : {
"name" : "stringValue"
}
}
}
That said, if you are interested in getting a json like this,
"properties": [
"ticketNumber",
"driver.name"
]
you will need a class (very simple one at that) that contains only a list of strings. properties is not an array of objects, but simply strings. From the looks of the FetchProperties method, you are creating an object with fetchProperties as the RootObject. Try something like this,
public class MyClass
{
[JsonProperty("fetchProperties")]
public Fetch FetchProperties { get; set; }
}
public class Fetch
{
[JsonProperty("properties")]
public List<string> Properties { get; set; }
}
private string FetchProperties()
{
MyClass obj = new MyClass()
{
FetchProperties = new Fetch()
{
Properties = new List<string>() { "ticketNumber", "driver.Name" }
}
};
return JsonConvert.SerializeObject(obj); // Formatting.None is by default
}
Now its your choice to hard code these values or, pass them as arguments or use a local variable that contains a list of all the strings you intend to store as "properties". You cant use enums because of violation in naming convention (driver.name) so these options should suffice.
I have a POST endpoint that takes a URL path param and then the body is a list of submitted DTOs.
So right now the request DTO looks something along the lines of:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
public string Param1 { get; set; }
public List<SomeEntry> Entries { get; set; }
}
public class SomeEntry
{
public int ID { get; set; }
public int Type { get; set; }
public string Value { get; set; }
}
And the service method looks something like:
public class SomeService : Service
{
public SomeResponse Post(SomeRequest request)
{
}
}
If encoded via JSON, the client would have to encode the POST body this way:
{
"Entries":
[
{
"id": 1
"type": 42
"value": "Y"
},
...
]
}
This is redundant, I would like the client to submit the data like this:
[
{
"id": 1
"type": 42
"value": "Y"
},
...
]
Which would have been the case if my request DTO was simply List<SomeEntry>
My questions is: is there a way to "flatten" the request this way? Or designate one property of the request as the root of the message body? i.e perhaps:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
public string Param1 { get; set; }
[MessageBody]
public List<SomeEntry> Entries { get; set; }
}
Is this doable in any way in ServiceStack?
I was able to sort of get this to sort of work by subclassing List<T>:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest : List<SomeEntry>
{
public string Param1 { get; set; }
}
Then you can send a request like this:
POST /prefix/someParameterValue
Content-Type: application/json
[ { "ID": 1, "Type": 2, "Value": "X" }, ... ]
But if you have any choice in the design, I wouldn't recommend this. Here's a couple of reasons to start with:
I found at least one problem with this at runtime: sending an empty array, e.g. [ ] in JSON, is resulting in a 400 status code with RequestBindingException
It's less flexible. What if you do need to add additional top-level properties to the request in the future? You would be stuck with them being path/query params. Having a regular class-containing-a-list allows you to add new optional properties at the top level of the request body, with backward compatibility
OK I've managed to achieve this. Not the prettiest solution but will do for now.
I wrapped the content type filter for JSON:
var serz = ContentTypeFilters.GetResponseSerializer("application/json");
var deserz = ContentTypeFilters.GetStreamDeserializer("application/json");
ContentTypeFilters.Register("application/json", serz, (type, stream) => MessageBodyPropertyFilter(type, stream, deserz));
Then the custom deserializer looks like this:
private object MessageBodyPropertyFilter(Type type, Stream stream, StreamDeserializerDelegate original)
{
PropertyInfo prop;
if (_messageBodyPropertyMap.TryGetValue(type, out prop))
{
var requestDto = type.CreateInstance();
prop.SetValue(requestDto, original(prop.PropertyType, stream), null);
return requestDto;
}
else
{
return original(type, stream);
}
}
_messageBodyPropertyMap is populated after init by scanning the request DTOs and looking for a certain attribute, as in the example in my original question.