We updated our Asp.Net application from .Net Framework 4.7.2 to .Net 5. Now we have problems with deserialization JSON in the controller methods. In old version we used Newtonsoft.Json. Former if we got a null for a property in JSON for a non nullable type like int the deserializer took the default value, or ignored the null and the error and did not overwrite the property's default value from object creation. But now after the error the whole object is set to null.
{
"effortType": "1",
"cwe": null,
"distanceInKilometers": null,
"effortDate": "2022-03-22T14:45:00+01:00",
"effortInHours": 1.0,
"hours25InHours": null,
"hours50InHours": null,
"hours100InHours": null,
"hours150InHours": null,
"orderNumber": "006001780872",
"withCosts": false,
"isNew": true,
"isEdited": false,
"isDeleted": false
}
public class OrderEffortDto
{
public string EffortType { get; set; }
public bool Cwe { get; set; }
public int? DistanceInKilometers { get; set; }
public DateTimeOffset? EffortDate { get; set; }
public decimal EffortInHours { get; set; }
public decimal Hours25InHours { get; set; }
public decimal Hours50InHours { get; set; }
public decimal Hours100InHours { get; set; }
public decimal Hours150InHours { get; set; }
public string OperationNumber { get; set; }
public bool IsNew { get; set; }
public bool IsEdited { get; set; }
public bool IsDeleted { get; set; }
}
Expected like before would be a OrderEffortDto instance with Cwe = false and all HoursXXInHours = 0
What we get is OrderEffortDto = null
We already tried to use Newtonsoft also in new version, but with the same result. We also configured SerializerSettings.NullValueHandling = NullValueHandling.Ignore
. This works for that problem, but than the null values are also ignored for the other direction, for serialization of DTOs into JSON, where the nulls are needed.
Is there a way to get back the old behavior? Right, it would be no problem to fix that in front end to get the right values into the JSON, but our application is big and to determine all the places, where we have to correct that is fault-prone.
Update 1 for those who may have the same problem
I created two simple test projects one ASP.Net WebApi with .Net Framework 4.7.2 and one ASP.Net WebApi with .Net 5, with above JSON and DTO example. I got two similar traces with errors from Newtonsoft and already described results for the DTO in the Controllers. Also the System.Text.Json in .Net 5 gave me a null for the whole DTO.
For API with .Net Framework 4.7.2
2022-03-24T10:50:05.368 Info Started deserializing WebApplication1NetFramework.Data.OrderEffortDto. Path 'effortType', line 2, position 16.
2022-03-24T10:50:05.388 Error Error deserializing WebApplication1NetFramework.Data.OrderEffortDto. Error converting value {null} to type 'System.Boolean'. Path 'cwe', line 3, position 14.
2022-03-24T10:50:05.403 Error Error deserializing WebApplication1NetFramework.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours25InHours', line 7, position 25.
2022-03-24T10:50:05.403 Error Error deserializing WebApplication1NetFramework.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours50InHours', line 8, position 25.
2022-03-24T10:50:05.403 Error Error deserializing WebApplication1NetFramework.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours100InHours', line 9, position 26.
2022-03-24T10:50:05.404 Error Error deserializing WebApplication1NetFramework.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours150InHours', line 10, position 26.
2022-03-24T10:50:05.404 Verbose Could not find member 'orderNumber' on WebApplication1NetFramework.Data.OrderEffortDto. Path 'orderNumber', line 11, position 17.
2022-03-24T10:50:05.405 Verbose Could not find member 'withCosts' on WebApplication1NetFramework.Data.OrderEffortDto. Path 'withCosts', line 12, position 15.
2022-03-24T10:50:05.407 Info Finished deserializing WebApplication1NetFramework.Data.OrderEffortDto. Path '', line 16, position 1.
2022-03-24T10:50:05.407 Verbose Deserialized JSON:
{
"effortType": "1",
"cwe": null,
"distanceInKilometers": null,
"effortDate": "2022-03-22T14:45:00+01:00",
"effortInHours": 1.0,
"hours25InHours": null,
"hours50InHours": null,
"hours100InHours": null,
"hours150InHours": null,
"orderNumber": "006001780872",
"withCosts": false,
"isNew": true,
"isEdited": false,
"isDeleted": false
}
For API with .Net 5
2022-03-24T10:48:19.162 Info Started deserializing WebApplication1NetCore.Data.OrderEffortDto. Path 'effortType', line 2, position 16.
2022-03-24T10:48:19.180 Error Error deserializing WebApplication1NetCore.Data.OrderEffortDto. Error converting value {null} to type 'System.Boolean'. Path 'cwe', line 3, position 14.
2022-03-24T10:48:19.196 Error Error deserializing WebApplication1NetCore.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours25InHours', line 7, position 25.
2022-03-24T10:48:19.196 Error Error deserializing WebApplication1NetCore.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours50InHours', line 8, position 25.
2022-03-24T10:48:19.197 Error Error deserializing WebApplication1NetCore.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours100InHours', line 9, position 26.
2022-03-24T10:48:19.197 Error Error deserializing WebApplication1NetCore.Data.OrderEffortDto. Error converting value {null} to type 'System.Decimal'. Path 'hours150InHours', line 10, position 26.
2022-03-24T10:48:19.197 Verbose Could not find member 'orderNumber' on WebApplication1NetCore.Data.OrderEffortDto. Path 'orderNumber', line 11, position 17.
2022-03-24T10:48:19.197 Verbose Could not find member 'withCosts' on WebApplication1NetCore.Data.OrderEffortDto. Path 'withCosts', line 12, position 15.
2022-03-24T10:48:19.199 Info Finished deserializing WebApplication1NetCore.Data.OrderEffortDto. Path '', line 16, position 1.
2022-03-24T10:48:19.200 Verbose Deserialized JSON:
{
"effortType": "1",
"cwe": null,
"distanceInKilometers": null,
"effortDate": "2022-03-22T14:45:00+01:00",
"effortInHours": 1.0,
"hours25InHours": null,
"hours50InHours": null,
"hours100InHours": null,
"hours150InHours": null,
"orderNumber": "006001780872",
"withCosts": false,
"isNew": true,
"isEdited": false,
"isDeleted": false
}
Thanks go out to @dbc for the comments. I will try it with the convertor in the mentioned post Json.net deserialization null guid case, but also will log the occurrences to fix the root cause.
Update 2
I altered the converter a little and used the "SwaggerGen.TypeExtensions.GetDefaultValue()". So I was able to remove the generic and use one converter for all non-nullable types.
public class NullToDefaultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var defaultValue = objectType.GetDefaultValue();
return defaultValue != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
// here I will add a logger to get all faulty calls
return objectType.GetDefaultValue();
return token.ToObject(objectType); // Deserialize using default serializer
}
// Return false I don't want default values to be written as null
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}