I've run into a type of issue.
I have an ErrorModel class, bare bones, that was inheriting ICollection.
public class ErrorsModel : ICollection<string>
{
private ICollection<string> Errors { get; set; }
public bool HasErrors { get { return Errors != null && Errors.Any(); } }
public string PartialView { get; set; }
}
Omitted the ICollection imp. as it is standard
When Returned by a controllers Action Json method
return Json(Errors)
The result is the private Errors object
[
"Select a Country",
"Select a City"
]
However when you remove the inheritance you get a complete serialization of the object.
{
"Count": 2,
"HasErrors": true,
"IsReadOnly": false
}
So my question is what is causing the odd serialization behavior when inheriting?
Your JSON serializer is treating all collection classes as arrays.
JSON does not support mixtures of arrays and objects.
Related
I'm parsing some JSON data I receive from a server using the built-in System.Text.Json module.
Here's an example class that I would use:
public class Something
{
[JsonPropertyName("items")]
public Item[] Items { get; set; }
}
The JSON data for this is usually received like the following, and it's properly parsed with JsonSerializer.Deserialize<Something>():
{
"items": [ { ... }, { ... }, { ... } ]
}
However, when there's no items, the server instead returns an empty object, which causes an exception because it expected an array.
{
"items": {}
}
Is there any way I could set it so that an empty object would be considered as an empty array? I've seen that you can make a custom JSON converter but I struggled to get it working.
you don't need any fansy custom converters in your case. Just try this
public class Something
{
[JsonPropertyName("items")]
public object _items
{
get
{
return Items;
}
set
{
if (((JsonElement)value).ValueKind.ToString() == "Array")
{
Items = ((JsonElement)value).Deserialize<Item[]>();
}
}
}
[System.Text.Json.Serialization.JsonIgnore]
public Item[] Items { get; set; }
}
and if you don't need to serialize it back, you can even remove _items get at all
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 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.
This question already has answers here:
Deserialize json with known and unknown fields
(5 answers)
Closed 6 years ago.
I'm doing some web API integration with Newtonsoft.Json, and as always, I have to do dumb stunts to properly deserialize what they're sending back.
In this case, the API will send responses resembling this kind of structure:
{ "contacts": [ ... ], "has-more": true, "offset": 38817 }
The "has-more" and "offset" properties are pretty much constant on the different method responses, and have been defined accordingly on the response object that I'm deserializing into. The response object looks something like this:
public class APIResponse {
public JContainer Result { get; set; }
[JsonProperty("has-more")]
public bool HasMore { get; set; }
[JsonProperty("offset")]
public int Offset { get; set; }
}
That first "contacts" property is what can vary; for some methods, I might get "contacts", some might get "companies", and others might get who-knows-what. I also don't have any way to be certain that every response will have such a "variable" property, nor that it will be the first one, positionally speaking.
For this example, what I would like to happen is the deserializer looks at the Json and says, "Let's see, I don't see anything mapping to 'contacts', so we'll put that into 'Result', and then I can see from the JsonProperty attributes that 'has-more' and 'offset' go into HasMore and Offset. Okay, all set, here's your object."
I suspect this involves some tricks with a custom JsonConverter or IContractResolver, but I'm just not connecting the dots here. I tried doing a simple custom contract resolver, but it appears to use contract resolvers to resolve object property names into property names to look for in the JSON text, not vice-versa.
You can use a base class + derivations for each response type.
public class APIResponseBase {
[JsonProperty("has-more")]
public bool HasMore { get; set; }
[JsonProperty("offset")]
public int Offset { get; set; }
}
public class ContactsResponse : APIResponseBase {
public IEnumerable<Contact> Contacts { get; set; }
}
public class CompaniesResponse : APIResponseBase {
public IEnumerable<Company> Companies { get; set; }
}
var contactsResponse = JsonConvert.Deserialize<ContactsResponse>(json);
IEnumerable<Contact> contacts = contactsResponse.Contacts
I implemented custom IExceptionToErrorInfoConverter for ASP.NET Boilerplate to convert custom exceptions in Web API.
Problem is that ASP.NET Boilerplate has a strict interface, that must return the ErrorInfo type:
ErrorInfo Convert(Exception exception);
The problem is that the ErrorInfo structure does not fit my requirements so I would like to have my own error DTO.
Anyone has an idea how to circumvent ASP.NET Boilerplate's exception conversion?
You can try one trick. When ASP.NET Boilerplate creates a JSON response, it might serialize the error with all available properties via reflection and wrap it into MvcAjaxResponse object with other stuff.
You can try to create your own class, derived from ErrorInfo and replace it at IExceptionToErrorInfoConverter implementation:
[Serializable]
public class MyErrorInfo : ErrorInfo
{
public string MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
}
public class MyExceptionToErrorInfoConverter : IExceptionToErrorInfoConverter
{
public IExceptionToErrorInfoConverter Next { set { } }
public ErrorInfo Convert(Exception exception)
{
return new MyErrorInfo{ MyProperty1 = "test", MyProperty2 = 1};
}
}
This is my complementary information based on Slava Utesinov's answer.
Indeed internally ASP.NET Boilerplate as one would suspect uses object when dealing with serializing DTOs, therefore assumption was solid.
Some sample code from ASP.NET Boilerplate's source:
public static string ToJsonString(this object obj, bool camelCase = false, bool indented = false)
{ ... }
protected override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
{ ... }
So after success I stepped up the problem to try and hide the original ErrorInfo member. Now, knowing that ASP.NET Boilerplate uses JSON.NET I found the feature of Conditional Property Serialization. By convention implementing bool ShouldSerialize[member name]() we can instruct the serializer to ignore a property.
So I ended up with following proof of concept code:
public class ErrorInfoEx : ErrorInfo
{
public new string Details { get; set; }
public bool ShouldSerializeDetails() { return false; }
public ErrorInfoEx(int code, string message) : base(code, message) { }
public string MyField { get; set; }
}
Pleas note, for some reason you must replace base class implementation to ignore base class members.
That resulted in the following JSON, as you can see there is no 'details' property but 'myField' is present.
{
"success":false,
"result":null,
"error":
{
"myField":"123",
"code":420,
"message":"Validation failed",
"validationErrors":
[{
"message":"'Order Number' should not be empty.",
"members":["OrderNumber"]
}]
},
"unAuthorizedRequest":false
}