Search code examples
c#deserializationsystem.text.json

Cannot deserialize a string to an int, despite having a converter and JsonNumberHandlingOptions


I am trying to deserialize the following JSON (an array with a single entry):

[
    {
        "name": "Luke Skywalker",
        "height": "172",
        "mass": "77",
        "birth_year": "19BBY",
        "gender": "male"
    }
]

Into this record:

internal record class Character
{
    public string Name { get; init; } = "";

    [property: JsonPropertyName("birth_year")]
    public double Birth { get; init; }

    public int Height { get; init; }

    [property: JsonPropertyName("mass")]
    public int Weight { get; init; }

    public GenderEnum Gender { get; init; }
}

With the following JsonSerializerOptions setup:

var options = new JsonSerializerOptions()
{
    PropertyNameCaseInsensitive = true,
    NumberHandling = JsonNumberHandling.AllowReadingFromString,
    Converters =
    {
        new GenderEnumConverter(),
        new BirthYearConverter(),
        new MeasurementsConverter()
    }
};

The two top converters work well. It's when I add the MeasurementsConverter that I get an exception:

internal class MeasurementsConverter : JsonConverter<int>
{
    public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.TryGetInt32(out int result) ? result : -1;
    }

    public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

The exception is thrown on the Read method:

System.InvalidOperationException: 'Cannot get the value of a token type 'String' as a number.'

When I deserialize with the following two expressions:

string result = await File.ReadAllTextAsync("people.json");
List<Character> people = JsonSerializer.Deserialize<List<Character>>(result, options);

Can you help me understand why the MeasurementsConverter : JsonConverter<int> is ignoring the NumberHandling = JsonNumberHandling.AllowReadingFromString option?

Note: This json is a sample from swapi.dev/api/people. If you know the API, at least one entry in the resulting array will have "unknown" as a value for the weight attribute. Hence, this converter.


Solution

  • From Jon's comment above, I was able to avoid triggering the exception by making explicit use of the Utf8JsonReader.TokenType property of the reader object.

    The Read() override now looks like this:

    public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if(reader.TokenType == JsonTokenType.String && options.NumberHandling == JsonNumberHandling.AllowReadingFromString)
        {
            return int.TryParse(reader.GetString(), out int result) ? result : -1;
        }
    
        return reader.GetInt32();
    }