Skip to content

JsonElement.GetInt32() fails when working with JsonElement directly #99229

@Skyppid

Description

@Skyppid

Description

We have a model class that represents our JSON data model. For some fields we have dynamic data types. Value can be string, integer, double, boolean, etc.

So for this we use a model class that defines the value as JsonElement so we can access the JsonValueKind and parse it depending on our needs and what it actually represents.

public sealed record DocumentValue(JsonElement Value, bool ValueFound, double Confidence, RelationalOperator? Operator, string Unit);

We parse our incoming JSON like this JsonSerializer.Deserialize<TMessage>(data, KafkaAdapterFactory.SerializationOptions) where in that case TMessage is our model which contains a list of DocumentValue at certain points in the structure.

Here's an example of the JSON (just a fragment) what we parse:

"fields": [
{
	"name": "Count",
	"value": {
		"value": 5,
		"valueFound": true,
		"confidence": 0,
		"operator": null,
		"unit": null
	}
}, // ...
]

As you see, it's clearly no floating point number (and will never be in that case). When I now set a breakpoint on this line:
var actualCount = count.Value.Value.GetInt32();

I get an FormatException. On the breakpoint I can verify that value kind is JsonValueKind.Number. So it should certainly be able to use GetInt32() without issues.

The exception looks like this:

System.FormatException: One of the identified items was in an invalid format.
at void System.Text.Json.ThrowHelper.ThrowFormatException()
at int System.Text.Json.JsonElement.GetInt32()

Checking the JsonElement with the debugger it says: Number: 5.0. When using GetDouble() and converting this into an int it works fine.

PS: To clarify, the setting used for deserialization are these:

    internal static JsonSerializerOptions SerializationOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
        PropertyNameCaseInsensitive = true,
        WriteIndented = false
    };

EDIT: More detailed information as I've stepped into it:

            if (Utf8Parser.TryParse(segment, out int tmp, out int consumed) &&
                consumed == segment.Length)

Here segment is a slice of 3 bytes, the call returns false, tmp: 5, consumed: 1, comsumed (1) == segment.Length (3). These are the values of it. So seems like while it parses it correctly, the check still fails due to the segment length mismatching the consumed parameter.

Reproduction Steps

Use a model with JsonElement as property type. Parse a JSON document where this value is represented as non-floating number i.e. "5". Deserialize using JsonSerializer.Deserialize<TMessage>() and evaluate the actual value. It should be of kind Number and GetInt32() should work on this element.

Expected behavior

JsonElement.GetInt32() should return a valid value. Even if it's internally represented as floating-point number, it should be able to convert it.

Actual behavior

A FormatException is being thrown.

Regression?

Can't tell, newly implemented feature that wasn't there before we upgraded to .NET 8.

Known Workarounds

Retrieving the value using JsonElement.GetDouble() and converting it works but is an unncessary extra step that shouldn't be necessary.

Configuration

.NET 8 (latest)
Windows 11, Linux (not sure which one exactly)
x64
Likely unrelated to configuration

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions