Introduction
System.Text.Json serializes enums as integers by default. To serialize as strings, use JsonStringEnumConverter. To customize the string name for individual enum values, use the JsonStringEnumMemberName attribute (.NET 9+) or the [EnumMember] attribute with a custom converter. Before .NET 9, there was no built-in way to specify custom enum names — you needed a custom JsonConverter or a third-party library like System.Text.Json.Serialization.JsonStringEnumMemberConverter.
Default Behavior
1using System.Text.Json;
2
3public enum Status
4{
5 Active,
6 Inactive,
7 PendingReview
8}
9
10var json = JsonSerializer.Serialize(Status.PendingReview);
11Console.WriteLine(json); // 2 (integer by default)
Without any configuration, enums serialize as their integer values. This is compact but not human-readable and breaks if enum values are reordered.
String Serialization with JsonStringEnumConverter
1using System.Text.Json;
2using System.Text.Json.Serialization;
3
4var options = new JsonSerializerOptions
5{
6 Converters = { new JsonStringEnumConverter() }
7};
8
9var json = JsonSerializer.Serialize(Status.PendingReview, options);
10Console.WriteLine(json); // "PendingReview"
11
12// Deserialization
13var status = JsonSerializer.Deserialize<Status>("\"PendingReview\"", options);
14Console.WriteLine(status); // PendingReview
15
16// Case-insensitive by default
17var status2 = JsonSerializer.Deserialize<Status>("\"pendingReview\"", options);
18Console.WriteLine(status2); // PendingReview
JsonStringEnumConverter serializes enum values as their C# member names. It supports case-insensitive deserialization by default.
Custom Names with JsonStringEnumMemberName (.NET 9+)
1using System.Text.Json;
2using System.Text.Json.Serialization;
3
4public enum OrderStatus
5{
6 [JsonStringEnumMemberName("new")]
7 New,
8
9 [JsonStringEnumMemberName("in-progress")]
10 InProgress,
11
12 [JsonStringEnumMemberName("shipped")]
13 Shipped,
14
15 [JsonStringEnumMemberName("cancelled")]
16 Cancelled
17}
18
19var options = new JsonSerializerOptions
20{
21 Converters = { new JsonStringEnumConverter() }
22};
23
24var json = JsonSerializer.Serialize(OrderStatus.InProgress, options);
25Console.WriteLine(json); // "in-progress"
26
27var status = JsonSerializer.Deserialize<OrderStatus>("\"in-progress\"", options);
28Console.WriteLine(status); // InProgress
JsonStringEnumMemberName is the built-in solution in .NET 9. It lets you specify the exact JSON string for each enum value, including lowercase, kebab-case, or any custom format.
Custom Names Before .NET 9 (EnumMember + Custom Converter)
1using System.Runtime.Serialization;
2using System.Text.Json;
3using System.Text.Json.Serialization;
4using System.Reflection;
5
6public enum Priority
7{
8 [EnumMember(Value = "low")]
9 Low,
10
11 [EnumMember(Value = "medium")]
12 Medium,
13
14 [EnumMember(Value = "high")]
15 High,
16
17 [EnumMember(Value = "critical")]
18 Critical
19}
20
21public class EnumMemberConverter<T> : JsonConverter<T> where T : struct, Enum
22{
23 public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
24 {
25 var value = reader.GetString();
26 foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static))
27 {
28 var attr = field.GetCustomAttribute<EnumMemberAttribute>();
29 if (attr?.Value == value || field.Name == value)
30 return (T)field.GetValue(null)!;
31 }
32 throw new JsonException($"Unknown value: {value}");
33 }
34
35 public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
36 {
37 var field = typeof(T).GetField(value.ToString()!);
38 var attr = field?.GetCustomAttribute<EnumMemberAttribute>();
39 writer.WriteStringValue(attr?.Value ?? value.ToString());
40 }
41}
42
43// Usage
44var options = new JsonSerializerOptions
45{
46 Converters = { new EnumMemberConverter<Priority>() }
47};
48
49var json = JsonSerializer.Serialize(Priority.Critical, options);
50Console.WriteLine(json); // "critical"
Before .NET 9, use [EnumMember(Value = "...")] from System.Runtime.Serialization combined with a custom converter that reads the attribute.
Naming Policy (camelCase, snake_case)
1using System.Text.Json;
2using System.Text.Json.Serialization;
3
4public enum Color
5{
6 DarkRed,
7 LightBlue,
8 ForestGreen
9}
10
11// camelCase naming policy
12var options = new JsonSerializerOptions
13{
14 Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
15};
16
17Console.WriteLine(JsonSerializer.Serialize(Color.DarkRed, options));
18// "darkRed"
19
20// Snake case (.NET 8+)
21var snakeOptions = new JsonSerializerOptions
22{
23 Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) }
24};
25
26Console.WriteLine(JsonSerializer.Serialize(Color.LightBlue, snakeOptions));
27// "light_blue"
JsonStringEnumConverter accepts a JsonNamingPolicy to transform all enum names. .NET 8 added SnakeCaseLower, SnakeCaseUpper, KebabCaseLower, and KebabCaseUpper policies.
Applying per Enum Type
1// Apply converter to a specific property
2public class Order
3{
4 public int Id { get; set; }
5
6 [JsonConverter(typeof(JsonStringEnumConverter))]
7 public OrderStatus Status { get; set; }
8
9 // This enum serializes as integer (no converter)
10 public Priority Priority { get; set; }
11}
12
13var order = new Order { Id = 1, Status = OrderStatus.Shipped, Priority = Priority.High };
14var json = JsonSerializer.Serialize(order);
15// {"Id":1,"Status":"Shipped","Priority":2}
Apply [JsonConverter] to individual properties to control serialization per field. Properties without the attribute use the default (integer) serialization.
Flags Enums
1[Flags]
2public enum Permissions
3{
4 None = 0,
5 Read = 1,
6 Write = 2,
7 Execute = 4,
8 All = Read | Write | Execute
9}
10
11var options = new JsonSerializerOptions
12{
13 Converters = { new JsonStringEnumConverter() }
14};
15
16var json = JsonSerializer.Serialize(Permissions.Read | Permissions.Write, options);
17Console.WriteLine(json); // "Read, Write"
18
19var perms = JsonSerializer.Deserialize<Permissions>("\"Read, Write\"", options);
20Console.WriteLine(perms); // Read, Write
JsonStringEnumConverter handles [Flags] enums by joining member names with commas. Deserialization parses the comma-separated string back into the combined flags value.
Common Pitfalls
No built-in custom names before .NET 9: JsonStringEnumMemberName was added in .NET 9. For earlier versions, you need a custom converter or the NuGet package Macross.Json.Extensions which provides [JsonStringEnumMemberName] compatibility.
JsonStringEnumConverter is case-insensitive by default: Deserialization matches "active", "Active", and "ACTIVE" to the same enum value. If you need strict casing, pass allowIntegerValues: false and validate manually.
Integer fallback on unknown strings: If deserialization encounters an unknown string, JsonStringEnumConverter throws JsonException. It does not silently default to 0 or the first enum value. Handle this with try-catch or input validation.
Naming policy overrides custom names: If you set both JsonNamingPolicy.CamelCase and [JsonStringEnumMemberName("custom")], the attribute takes precedence over the naming policy in .NET 9+.
Flags enum with unnamed combinations: Permissions.Read | Permissions.Execute serializes as "Read, Execute". But the value 3 (Read | Write) requires both names to be defined — if the combination is unnamed, it serializes as the integer 3 unless all individual flags are defined.
Summary
Use JsonStringEnumConverter to serialize enums as strings instead of integers
Use [JsonStringEnumMemberName("name")] in .NET 9+ for per-value custom names
Before .NET 9, use [EnumMember(Value = "name")] with a custom converter
Use JsonNamingPolicy.CamelCase or .SnakeCaseLower for automatic name transformation
Apply [JsonConverter] per property to mix string and integer enum serialization
[Flags] enums serialize as comma-separated member names