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):

  1. Create a partial class that derives from JsonSerializerContext.

  2. Specify the type to serialize or deserialize by applying JsonSerializableAttribute to the context class.

  3. Call a JsonSerializer method that either:

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.

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":0,"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 a TypeInfo<TValue>. Pass it the Default.<TypeName> property of your context class:

    jsonString = JsonSerializer.Serialize(
        weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
    
  • A JsonSerializer.Serialize method that takes a context. Pass it the Default 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 or TypeInfo<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(
        new JsonSerializerOptions(JsonSerializerDefaults.Web)));
weatherForecast = JsonSerializer.Deserialize(
    jsonString, 
    typeof(WeatherForecast), 
    new OptionsExampleContext(
        new JsonSerializerOptions(JsonSerializerDefaults.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(
                    new JsonSerializerOptions(JsonSerializerDefaults.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(
                    new JsonSerializerOptions(JsonSerializerDefaults.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
}

See also