How to use source generation in System.Text.Json
Source generation in System.Text.Json is available in .NET 6 and later versions. When used in an app, the app's language version must be C# 9.0 or later. This article shows you how to use source-generation-backed serialization in your apps.
For information about the different source-generation modes, see Source-generation modes.
Use source-generation defaults
To use source generation with all defaults (both modes, default options):
Create a partial class that derives from JsonSerializerContext.
Specify the type to serialize or deserialize by applying JsonSerializableAttribute to the context class.
Call a JsonSerializer method that either:
- Takes a JsonTypeInfo<T> instance, or
- Takes a JsonSerializerContext instance, or
- Takes a JsonSerializerOptions instance and you've set its JsonSerializerOptions.TypeInfoResolver property to the
Default
property of the context type (.NET 7 and later only).
By default, both source generation modes are used if you don't specify one. For information about how to specify the mode to use, see Specify source generation mode later in this article.
Here's the type that is used in the following examples:
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
Here's the context class configured to do source generation for the preceding WeatherForecast
class:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
The types of WeatherForecast
members don't need to be explicitly specified with [JsonSerializable]
attributes. Members declared as object
are an exception to this rule. The runtime type for a member declared as object
needs to be specified. For example, suppose you have the following class:
public class WeatherForecast
{
public object? Data { get; set; }
public List<object>? DataList { get; set; }
}
And you know that at runtime it may have boolean
and int
objects:
WeatherForecast wf = new() { Data = true, DataList = new List<object> { true, 1 } };
Then boolean
and int
have to be declared as [JsonSerializable]
:
[JsonSerializable(typeof(WeatherForecast))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
public partial class WeatherForecastContext : JsonSerializerContext
{
}
To specify source generation for a collection, use [JsonSerializable]
with the collection type. For example: [JsonSerializable(typeof(List<WeatherForecast>))]
.
JsonSerializer
methods that use source generation
In the following examples, the static Default
property of the context type provides an instance of the context type with default options. The context instance provides a WeatherForecast
property that returns a JsonTypeInfo<WeatherForecast>
instance. You can specify a different name for this property by using the TypeInfoPropertyName property of the [JsonSerializable]
attribute.
Serialization examples
Using JsonTypeInfo<T>:
jsonString = JsonSerializer.Serialize(
weatherForecast!, SourceGenerationContext.Default.WeatherForecast);
Using JsonSerializerContext:
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default);
Using JsonSerializerOptions:
sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), sourceGenOptions);
Deserialization examples
Using JsonTypeInfo<T>:
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
jsonString, SourceGenerationContext.Default.WeatherForecast);
Using JsonSerializerContext:
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), SourceGenerationContext.Default)
as WeatherForecast;
Using JsonSerializerOptions:
var sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), sourceGenOptions)
as WeatherForecast;
Complete program example
Here are the preceding examples in a complete program:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace BothModesNoOptions
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
public class Program
{
public static void Main()
{
string jsonString = """
{
"Date": "2019-08-01T00:00:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
""";
WeatherForecast? weatherForecast;
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
jsonString, SourceGenerationContext.Default.WeatherForecast);
Console.WriteLine($"Date={weatherForecast?.Date}");
// output:
//Date=8/1/2019 12:00:00 AM
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), SourceGenerationContext.Default)
as WeatherForecast;
Console.WriteLine($"Date={weatherForecast?.Date}");
// output:
//Date=8/1/2019 12:00:00 AM
var sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), sourceGenOptions)
as WeatherForecast;
Console.WriteLine($"Date={weatherForecast?.Date}");
// output:
//Date=8/1/2019 12:00:00 AM
jsonString = JsonSerializer.Serialize(
weatherForecast!, SourceGenerationContext.Default.WeatherForecast);
Console.WriteLine(jsonString);
// output:
//{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default);
Console.WriteLine(jsonString);
// output:
//{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), sourceGenOptions);
Console.WriteLine(jsonString);
// output:
//{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
}
}
}
Specify source-generation mode
You can specify metadata-based mode or serialization-optimization mode for an entire context, which may include multiple types. Or you can specify the mode for an individual type. If you do both, the mode specification for a type wins.
- For an entire context, use the JsonSourceGenerationOptionsAttribute.GenerationMode property.
- For an individual type, use the JsonSerializableAttribute.GenerationMode property.
Serialization-optimization (fast path) mode example
For an entire context:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializeOnlyContext : JsonSerializerContext { }
For an individual type:
[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext { }
Complete program example
using System.Text.Json; using System.Text.Json.Serialization; namespace SerializeOnlyNoOptions { public class WeatherForecast { public DateTime Date { get; set; } public int TemperatureCelsius { get; set; } public string? Summary { get; set; } } [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializeOnlyContext : JsonSerializerContext { } [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext { } public class Program { public static void Main() { string jsonString; WeatherForecast weatherForecast = new() { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" }; // Use context that selects Serialization mode only for WeatherForecast. jsonString = JsonSerializer.Serialize(weatherForecast, SerializeOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"} // Use a context that selects Serialization mode. jsonString = JsonSerializer.Serialize(weatherForecast, SerializeOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"} } } }
Metadata-based mode example
For an entire context:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(WeatherForecast))] internal partial class MetadataOnlyContext : JsonSerializerContext { }
jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyContext.Default.WeatherForecast);
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>( jsonString, MetadataOnlyContext.Default.WeatherForecast);
For an individual type:
[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext { }
jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>( jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
Complete program example
using System.Text.Json; using System.Text.Json.Serialization; namespace MetadataOnlyNoOptions { public class WeatherForecast { public DateTime Date { get; set; } public int TemperatureCelsius { get; set; } public string? Summary { get; set; } } [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext { } [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(WeatherForecast))] internal partial class MetadataOnlyContext : JsonSerializerContext { } public class Program { public static void Main() { string jsonString = """ { "Date": "2019-08-01T00:00:00", "TemperatureCelsius": 25, "Summary": "Hot" } """; WeatherForecast? weatherForecast; // Deserialize with context that selects metadata mode only for WeatherForecast only. weatherForecast = JsonSerializer.Deserialize<WeatherForecast>( jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine($"Date={weatherForecast?.Date}"); // output: //Date=8/1/2019 12:00:00 AM // Serialize with context that selects metadata mode only for WeatherForecast only. jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"} // Deserialize with context that selects metadata mode only. weatherForecast = JsonSerializer.Deserialize<WeatherForecast>( jsonString, MetadataOnlyContext.Default.WeatherForecast); Console.WriteLine($"Date={weatherForecast?.Date}"); // output: //Date=8/1/2019 12:00:00 AM // Serialize with context that selects metadata mode only. jsonString = JsonSerializer.Serialize( weatherForecast!, MetadataOnlyContext.Default.WeatherForecast); Console.WriteLine(jsonString); // output: //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"} } } }
Source-generation support in ASP.NET Core
In Blazor apps, use overloads of HttpClientJsonExtensions.GetFromJsonAsync and HttpClientJsonExtensions.PostAsJsonAsync extension methods that take a source generation context or TypeInfo<TValue>
.
Starting with .NET 8, you can also use overloads of HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable extension methods that accept a source generation context or TypeInfo<TValue>
.
In Razor Pages, MVC, SignalR, and Web API apps, use the JsonSerializerOptions.TypeInfoResolver property to specify the context.
[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
var serializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = MyJsonContext.Default
};
services.AddControllers().AddJsonOptions(
static options =>
options.JsonSerializerOptions.TypeInfoResolverChain.Add(MyJsonContext.Default));
In Razor Pages, MVC, SignalR, and Web API apps, use the JsonSerializerOptions.TypeInfoResolver property to specify the context.
[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
var serializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = MyJsonContext.Default
};
services.AddControllers().AddJsonOptions(
static options =>
options.JsonSerializerOptions = serializerOptions);
In Razor Pages, MVC, SignalR, and Web API apps, use the AddContext method of JsonSerializerOptions, as shown in the following example:
[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.AddContext<MyJsonContext>());
Note
JsonSourceGenerationMode.Serialization, or fast-path serialization, isn't supported for asynchronous serialization.
In .NET 7 and earlier versions, this limitation also applies to synchronous overloads of JsonSerializer.Serialize that accept a Stream. Starting with .NET 8, even though streaming serialization requires metadata-based models, it will fall back to fast-path if the payloads are known to be small enough to fit in the predetermined buffer size. For more information, see https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#json.
Disable reflection defaults
Because System.Text.Json uses reflection by default, calling a basic serialization method can break Native AOT apps, which doesn't support all required reflection APIs. These breaks can be challenging to diagnose since they can be unpredictable, and apps are often debugged using the CoreCLR runtime, where reflection works. Instead, if you explicitly disable reflection-based serialization, breaks are easier to diagnose. Code that uses reflection-based serialization will cause an InvalidOperationException with a descriptive message to be thrown at run time.
To disable default reflection in your app, set the JsonSerializerIsReflectionEnabledByDefault
MSBuild property to false
in your project file:
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
- The behavior of this property is consistent regardless of runtime, either CoreCLR or Native AOT.
- If you don't specify this property and PublishTrimmed is enabled, reflection-based serialization is automatically disabled.
You can programmatically check whether reflection is disabled by using the JsonSerializer.IsReflectionEnabledByDefault property. The following code snippet shows how you might configure your serializer depending on whether reflection is enabled:
static JsonSerializerOptions CreateDefaultOptions()
{
return new()
{
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault
? new DefaultJsonTypeInfoResolver()
: MyContext.Default
};
}
Because the property is treated as a link-time constant, the previous method doesn't root the reflection-based resolver in applications that run in Native AOT.
Specify options
In .NET 8 and later versions, most options that you can set using JsonSerializerOptions can also be set using the JsonSourceGenerationOptionsAttribute attribute. The advantage to setting options via the attribute is that the configuration is specified at compile time, which ensures that the generated MyContext.Default
property is preconfigured with all the relevant options set.
The following code shows how to set options using the JsonSourceGenerationOptionsAttribute attribute.
[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SerializationModeOptionsContext : JsonSerializerContext
{
}
When using JsonSourceGenerationOptionsAttribute
to specify serialization options, call one of the following serialization methods:
A
JsonSerializer.Serialize
method that takes aTypeInfo<TValue>
. Pass it theDefault.<TypeName>
property of your context class:jsonString = JsonSerializer.Serialize( weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
A
JsonSerializer.Serialize
method that takes a context. Pass it theDefault
static property of your context class.jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default);
If you call a method that lets you pass in your own instance of Utf8JsonWriter
, the writer's Indented setting is honored instead of the JsonSourceGenerationOptionsAttribute.WriteIndented
option.
If you create and use a context instance by calling the constructor that takes a JsonSerializerOptions
instance, the supplied instance will be used instead of the options specified by JsonSourceGenerationOptionsAttribute
.
Here are the preceding examples in a complete program:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SerializeOnlyWithOptions
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SerializationModeOptionsContext : JsonSerializerContext
{
}
public class Program
{
public static void Main()
{
string jsonString;
WeatherForecast weatherForecast = new()
{ Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" };
// Serialize using TypeInfo<TValue> provided by the context
// and options specified by [JsonSourceGenerationOptions].
jsonString = JsonSerializer.Serialize(
weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
Console.WriteLine(jsonString);
// output:
//{
// "date": "2019-08-01T00:00:00",
// "temperatureCelsius": 0,
// "summary": "Hot"
//}
// Serialize using Default context
// and options specified by [JsonSourceGenerationOptions].
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default);
Console.WriteLine(jsonString);
// output:
//{
// "date": "2019-08-01T00:00:00",
// "temperatureCelsius": 0,
// "summary": "Hot"
//}
}
}
}
Specify options by using JsonSerializerOptions
Some options of JsonSerializerOptions can't be set using JsonSourceGenerationOptionsAttribute. To specify options by using JsonSerializerOptions:
- Create an instance of
JsonSerializerOptions
. - Create an instance of your class that derives from JsonSerializerContext, and pass the
JsonSerializerOptions
instance to the constructor. - Call serialization or deserialization methods of
JsonSerializer
that take a context instance orTypeInfo<TValue>
.
Here's an example context class followed by serialization and deserialization example code:
[JsonSerializable(typeof(WeatherForecast))]
internal partial class OptionsExampleContext : JsonSerializerContext
{
}
jsonString = JsonSerializer.Serialize(
weatherForecast,
typeof(WeatherForecast),
new OptionsExampleContext(
JsonSerializerOptions.Web));
weatherForecast = JsonSerializer.Deserialize(
jsonString,
typeof(WeatherForecast),
new OptionsExampleContext(
JsonSerializerOptions.Web))
as WeatherForecast;
Here are the preceding examples in a complete program:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JsonSerializerOptionsExample
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
[JsonSerializable(typeof(WeatherForecast))]
internal partial class OptionsExampleContext : JsonSerializerContext
{
}
public class Program
{
public static void Main()
{
string jsonString = """
{
"date": "2019-08-01T00:00:00",
"temperatureCelsius": 25,
"summary": "Hot"
}
""";
WeatherForecast? weatherForecast;
weatherForecast = JsonSerializer.Deserialize(
jsonString,
typeof(WeatherForecast),
new OptionsExampleContext(
JsonSerializerOptions.Web))
as WeatherForecast;
Console.WriteLine($"Date={weatherForecast?.Date}");
// output:
//Date=8/1/2019 12:00:00 AM
jsonString = JsonSerializer.Serialize(
weatherForecast,
typeof(WeatherForecast),
new OptionsExampleContext(
JsonSerializerOptions.Web));
Console.WriteLine(jsonString);
// output:
//{ "date":"2019-08-01T00:00:00","temperatureCelsius":25,"summary":"Hot"}
}
}
}
Combine source generators
You can combine contracts from multiple source-generated contexts inside a single JsonSerializerOptions instance. Use the JsonSerializerOptions.TypeInfoResolver property to chain multiple contexts that have been combined by using the JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]) method.
var options = new JsonSerializerOptions
{
TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default);
};
Starting in .NET 8, if you later want to prepend or append another context, you can do so using the JsonSerializerOptions.TypeInfoResolverChain property. The ordering of the chain is significant: JsonSerializerOptions queries each of the resolvers in their specified order and returns the first result that's non-null.
options.TypeInfoResolverChain.Add(ContextD.Default); // Append to the end of the list.
options.TypeInfoResolverChain.Insert(0, ContextE.Default); // Insert at the beginning of the list.
Any change made to the TypeInfoResolverChain property is reflected by TypeInfoResolver and vice versa.
Serialize enum fields as strings
By default, enums are serialized as numbers. To serialize a specific enum's fields as strings when using source generation, annotate it with the JsonStringEnumConverter<TEnum> converter. Or to set a blanket policy for all enumerations, use the JsonSourceGenerationOptionsAttribute attribute.
JsonStringEnumConverter<T>
converter
To serialize enum names as strings using source generation, use the JsonStringEnumConverter<TEnum> converter. (The non-generic JsonStringEnumConverter type is not supported by the Native AOT runtime.)
Annotate the enumeration type with the JsonStringEnumConverter<TEnum> converter using the JsonConverterAttribute attribute:
public class WeatherForecastWithPrecipEnum
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public Precipitation? Precipitation { get; set; }
}
[JsonConverter(typeof(JsonStringEnumConverter<Precipitation>))]
public enum Precipitation
{
Drizzle, Rain, Sleet, Hail, Snow
}
Create a JsonSerializerContext class and annotate it with the JsonSerializableAttribute attribute:
[JsonSerializable(typeof(WeatherForecastWithPrecipEnum))]
public partial class Context1 : JsonSerializerContext { }
The following code serializes the enum names instead of the numeric values:
var weatherForecast = new WeatherForecastWithPrecipEnum
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Precipitation = Precipitation.Sleet
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
TypeInfoResolver = Context1.Default,
};
string? jsonString = JsonSerializer.Serialize(weatherForecast, options);
The resulting JSON looks like the following example:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Precipitation": "Sleet"
}
Blanket policy
Instead of using the JsonStringEnumConverter<TEnum> type, you can apply a blanket policy to serialize enums as strings by using the JsonSourceGenerationOptionsAttribute. Create a JsonSerializerContext class and annotate it with the JsonSerializableAttribute and JsonSourceGenerationOptionsAttribute attributes:
[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
[JsonSerializable(typeof(WeatherForecast2WithPrecipEnum))]
public partial class Context2 : JsonSerializerContext { }
Notice that the enum doesn't have the JsonConverterAttribute:
public class WeatherForecast2WithPrecipEnum
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public Precipitation2? Precipitation { get; set; }
}
public enum Precipitation2
{
Drizzle, Rain, Sleet, Hail, Snow
}
Custom enum member names
Starting in .NET 9, you can customize enum member names using the JsonStringEnumMemberName attribute. For more information, see Custom enum member names.