JsonException: The JSON value could not be converted to Priority, where
Priority is your enum, appeared here when the JSON carried the enum as a name
("High") and the serializer ran with default options. Add
JsonStringEnumConverter to JsonSerializerOptions.Converters and
the name is read as the enum member.
With the converter in place the same JSON produced High, and serializing the
object back wrote "Priority":"High" as a string too.
The error
Unhandled exception. System.Text.Json.JsonException: The JSON value could not be converted to Priority. Path: $.Priority | LineNumber: 0 | BytePositionInLine: 26.
at System.Text.Json.ThrowHelper.ThrowJsonException(String message)
at System.Text.Json.Serialization.Converters.EnumConverter`1.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
...
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
at Program.<Main>$(String[] args) in C:\...\en\Program.cs:line 16
Why it happens
The JSON was {"Id":42,"Priority":"High"} and the class was:
public class Ticket
{
public int Id { get; set; }
public Priority Priority { get; set; }
}
public enum Priority { Low, Normal, High }
The second stack frame names the code that refused the value:
EnumConverter`1.Read, the enum converter the serializer used with default
options. The fix output below shows the other side of it: (int)Priority.High
is 2, and before the fix the same Ticket could not be read from
the name "High". Unlike the DateTime case, this exception has no
inner exception; the message, the path $.Priority and the byte position are
all there is, and they are enough to find the property.
Counting the bytes confirms it: {"Id":42, is 9 bytes,
"Priority": is 11 more and "High" is 6, so
BytePositionInLine: 26 is the byte right after the string value the enum
converter refused.
The fix
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
var fixedTicket = JsonSerializer.Deserialize<Ticket>(json, options)!;
Console.WriteLine($"{fixedTicket.Id} {fixedTicket.Priority} {(int)fixedTicket.Priority}");
Console.WriteLine(JsonSerializer.Serialize(fixedTicket, options));
Running dotnet run -- fix printed:
42 High 2
{"Id":42,"Priority":"High"}
The first line shows the name read into the enum (High, value
2); the second shows the same options writing the enum back as its name, so
a client that sent names gets names in return. Register the converter on the options
before their first use: options that have already serialized something refuse a new
converter, as the read-only options post below shows.
How it was reproduced
A console project from dotnet new console on .NET SDK 10.0.403
(net10.0, no extra packages; System.Text.Json is part of the framework), the
Ticket class and Priority enum above, and
JsonSerializer.Deserialize<Ticket>(json) with default options. Run with
dotnet run for the error and dotnet run -- fix for the options
with JsonStringEnumConverter.
Frequently asked
- How do I deserialize an enum from a string with System.Text.Json?
- Add new JsonStringEnumConverter() to JsonSerializerOptions.Converters and pass those options to Deserialize. In the test the JSON value High was then read as Priority.High.
- Does JsonStringEnumConverter also change serialization?
- Yes, in the test the same options wrote the enum back as "Priority":"High" instead of a number.
- Why is there no inner exception for the enum error?
- In the captured run the exception was thrown directly by EnumConverter.Read through ThrowHelper.ThrowJsonException, so the JsonException stands alone with the path $.Priority and the byte position.
More decoded errors in the Fixes category. Adding the converter after the options were used gives This JsonSerializerOptions instance is read-only.