Hossein Fallah
Hossein Fallah

Reputation: 2539

System.Text.Json.JsonElement.GetString fails for numeric values

Consider these two JSONs:

{
    "name": "John",
    "age": 40
}

and

{
    "name": "John",
    "age": "40"
}

The only difference is that in one, 40 is a number, in the other it's a string.

I need to parse these JSONs using System.Text.Json and my code fails:

System.InvalidOperationException : Cannot get the value of a token type 'Number' as a string.

I deserialize the JSON and then use JsonElement.GetString() to get the values.

How can I fix this?

Upvotes: 1

Views: 1523

Answers (1)

Peter Csala
Peter Csala

Reputation: 22714

If you have a JsonElement then you can use its ValueKind property to determine the type of the json which it represents.

So, for instance if you have parsed a JsonDocument and you retrieve its RootElement then that JsonElement's ValueKind can be either Array or Object.

If you have a JsonElement by calling the GetProperty then the returned JsonElement's ValueKind can be any of the predefined values.


In your particular example you can use the ValueKind to determine which GetXYZ method should be called based on the fetched property's data type.

string input1 = "{\"name\": \"John\", \"age\": 40}";
string input2 = "{\"name\": \"John\", \"age\": \"40\"}";

foreach (var input in new[] { input1, input2 })
{
    var doc = JsonDocument.Parse(input);
    var age = doc.RootElement.GetProperty("age");
    switch (age.ValueKind)
    {
        case JsonValueKind.Number:
        {
            var value = age.GetInt32();
            Console.WriteLine($"{value} as a number");
            break;
        }
        case JsonValueKind.String:
        {
            var value = age.GetString();
            Console.WriteLine($"{value} as a text");
            break;
        }
        default:
            Console.WriteLine("unknown type");
            break;
    }
}

The above code is a bit fragile, since I've picked GetInt32 if the ValueKind is Number. But if you don't know upfront the number's data type then you should use the TryGetXYZ methods to try to fetch numbers in a safe way.

string input1 = "{\"name\": \"John\", \"age\": 40}";
string input2 = "{\"name\": \"John\", \"age\": \"40\"}";
string input3 = "{\"name\": \"John\", \"age\": 40.0}";

foreach (var input in new[] { input1, input2, input3 })
{
    var doc = JsonDocument.Parse(input);
    var age = doc.RootElement.GetProperty("age");
    switch (age.ValueKind)
    {
        case JsonValueKind.Number:
            {
                if(age.TryGetInt32(out var integralNumber))
                    Console.WriteLine($"{integralNumber} as an integral number");
                else if (age.TryGetDouble(out var floatingNumber))
                    Console.WriteLine($"{floatingNumber} as a floating number");
                break;
            }
        case JsonValueKind.String:
            {
                var value = age.GetString();
                Console.WriteLine($"{value} as a text");
                break;
            }
        default:
            Console.WriteLine("unknown type");
            break;
    }
}

Upvotes: 2

Related Questions