Home > Back-end >  Exception problem following .NET 5 migration
Exception problem following .NET 5 migration

Time:09-18

I have just migrated a dotnet core 3.1 project to dotnet 5.0 and since then I have encountered a problem with my unit tests. One of my unit test is testing the error returned when deserializing an AccountDto object (converter test).

Before my dotnet 5.0 migration, I got the following error message in my unit test catch: "The record type id '' is not supported". Since the migration, the exception caught has changed: "The record type id '' is not supported. The unsupported member type is located on type 'System.String'. Path: $.RecordTypeId | LineNumber: 0 | BytePositionInLine: 64."

By analyzing the content of the exception, I noticed that my exception thrown in my RecordTypeConverter.Read() is indeed present but in the inner exception.

Unit test :

[Theory]
[InlineData("", null, "The record type id '' is not supported.")]
protected void MapAccountRecordTypeTest(string recordTypeId, string recordTypeExpected, string errorExpected)
{
    var account = new AccountDto();
    var error = string.Empty;

    var accountDto = new AccountDto
    {
        RecordType = recordTypeId
    };
    var myjson = JsonSerializer.Serialize(accountDto);

    try
    {
        account = JsonSerializer.Deserialize<AccountDto>(myjson);
    }
    catch (Exception ex)
    {
        error = ex.Message;
    }

    Assert.Equal(errorExpected, error);
}

Code called by the unit test and throwing the exception i would like to catch :

public class RecordTypeConverter : JsonConverter<string>
{
    private readonly Dictionary<string, string> _accountStatus = new()
    {
        { Constant.ENTERPRISE_RECORD_TYPE_ID, Constant.ENTERPRISE_RECORD_TYPE },
        { Constant.INDIVIDUAL_RECORD_TYPE_ID, Constant.INDIVIDUAL_RECORD_TYPE }
    };

    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string value = reader.GetString();
        if (value != null)
        {
            string key = value.ToUpper();
            if (!_accountStatus.ContainsKey(key))
            {
                throw new NotSupportedException($"The record type id '{key}' is not supported.");
            }

            return _accountStatus[key];
        }

        return null;
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
        => writer.WriteStringValue(value);
}

Exception caught:

à System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, NotSupportedException ex)
   à System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   à System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   à System.Text.Json.JsonSerializer.ReadCore[TValue](Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
   à System.Text.Json.JsonSerializer.Deserialize[TValue](String json, Type returnType, JsonSerializerOptions options)
   à System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   à tests.AccountTests.MapAccountRecordTypeTest(String recordTypeId, String recordTypeExpected, String errorExpected) dans D:\\XXXXX\\AccountTests.cs :ligne 72

Exception present in the inner exception of the above exception. It is this exception that I was supposed to retrieve:

à SlxConnector.Helpers.Converters.RecordTypeConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options) dans d:\\XXXXXXX\\src\\Helpers\\Converters\\RecordTypeConverter.cs :ligne 27
   à System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   à System.Text.Json.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   à System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   à System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   à System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)

I know how to adapt my unit test to make it work, but I would like to understand why when I migrated my project to .NET 5, the behaviour changed. Apparently, the .NET 5 JsonSerializer raises a new exception from my exception ???

CodePudding user response:

After migrating to .NET 5, a new version of the .NET runtime is used by your code. This new version offers the same public interface to access the functionality ("how your code can request deserialization"), but the internal implementation ("how deserialization is done") is not the same.
Builders of APIs and runtimes try to keep the public interface constant as long as possible, but it is not unusual that the internal implementation is changed. This is done in order to incorporate bug fixes, improvements or security patches.

In your specific case, you get more information than before - which I'd also consider to be good. As you already suggest, adjusting your unit test is the preferred way to handle this.

If you want to analyze the changes in the .NET runtime, you can have a look at the code for JSON serialization.

CodePudding user response:

I haven't checked exactly which version changes this but the change was made on March 6, 2020. Now, when you throw a NotSupportedException without a " Path: " in Message, it will use the ThrowHelper class to rethrow.

Comments from source code :

If the message already contains Path, just re-throw. This could occur in serializer re-entry cases. To get proper Path semantics in re-entry cases, APIs that take 'state' need to be used.

You can find the source code here.

To resolve your issue, you should add a "Path:" in the message of your exception, or use a custom Exception. You should also verify that a JsonException is not simply the most appropriate solution because your are checking that the JSON is in a proper format before deserializing.

  • Related