System.Text.Json でソース生成を使用する方法
System.Text.Json でのソース生成は、.NET 6 以降のバージョンで可能です。 アプリで使用する場合、アプリの言語バージョンは C# 9.0 以降である必要があります。 この記事では、ソース生成でサポートされるシリアル化をアプリで使用する方法について説明します。
さまざまなソース生成モードの詳細については、ソース生成モードに関する記事を参照してください。
ソース生成の既定値を使用する
すべての既定値を使用してソース生成を使用する場合 (両方のモード、既定のオプション):
JsonSerializerContext から派生する部分クラスを作成します。
コンテキスト クラスに JsonSerializableAttribute を適用して、シリアル化または逆シリアル化する型を指定します。
次のいずれかの JsonSerializer メソッドを呼び出します。
- JsonTypeInfo<T> インスタンスを受け取る。または
- JsonSerializerContext インスタンスを受け取る。または
- インスタンスを JsonSerializerOptions 受け取り、その JsonSerializerOptions.TypeInfoResolver プロパティをコンテキスト型の
Default
プロパティに設定済み (.NET 7 以降のみ)。
既定では、いずれも指定しない場合、両方のソース生成モードが使用されます。 使用するモードを指定する方法については、この記事で後述する「ソース生成モードを指定する」を参照してください。
使用される型を次の例に示します。
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
前の WeatherForecast
クラスのソース生成を行うように構成されたコンテキスト クラスを次に示します。
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
WeatherForecast
メンバーの型は、[JsonSerializable]
属性で明示的に指定する必要はありません。 object
として宣言されたメンバーは、この規則の例外です。 object
として宣言されたメンバーのランタイム型は指定する必要があります。 たとえば、次のクラスがあるとします。
public class WeatherForecast
{
public object? Data { get; set; }
public List<object>? DataList { get; set; }
}
また、実行時に、boolean
と int
オブジェクトを持てることを知っています。
WeatherForecast wf = new() { Data = true, DataList = new List<object> { true, 1 } };
次に、boolean
および int
を [JsonSerializable]
として宣言する必要があります。
[JsonSerializable(typeof(WeatherForecast))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
public partial class WeatherForecastContext : JsonSerializerContext
{
}
コレクションのソース生成を指定するには、コレクション型と共に [JsonSerializable]
を使用します。 (例: [JsonSerializable(typeof(List<WeatherForecast>))]
)。
ソース生成を使用する JsonSerializer
メソッド
次の例では、コンテキスト型の静的 Default
プロパティは、既定のオプションを持つコンテキスト型のインスタンスを提供します。 コンテキスト インスタンスは、JsonTypeInfo<WeatherForecast>
インスタンスを返す WeatherForecast
プロパティを提供します。 [JsonSerializable]
属性の TypeInfoPropertyName プロパティを使用して、このプロパティに別の名前を指定できます。
シリアル化の例
jsonString = JsonSerializer.Serialize(
weatherForecast!, SourceGenerationContext.Default.WeatherForecast);
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default);
sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
jsonString = JsonSerializer.Serialize(
weatherForecast, typeof(WeatherForecast), sourceGenOptions);
逆シリアル化の例
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
jsonString, SourceGenerationContext.Default.WeatherForecast);
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), SourceGenerationContext.Default)
as WeatherForecast;
var sourceGenOptions = new JsonSerializerOptions
{
TypeInfoResolver = SourceGenerationContext.Default
};
weatherForecast = JsonSerializer.Deserialize(
jsonString, typeof(WeatherForecast), sourceGenOptions)
as WeatherForecast;
完全なプログラムの例
前の例の完全なプログラムを次に示します。
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"}
}
}
}
ソース生成モードを指定する
メタデータベース モードまたはシリアル化の最適化モードは、複数の型を含む可能性があるコンテキスト全体に対して指定できます。 または、個々の型に対してモードを指定することもできます。 両方を行った場合は、型に対するモード指定が有効になります。
- コンテキスト全体には、JsonSourceGenerationOptionsAttribute.GenerationMode プロパティを使用します。
- 個々の型には、JsonSerializableAttribute.GenerationMode プロパティを使用します。
シリアル化の最適化 (高速パス) モードの例
コンテキスト全体の場合:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(WeatherForecast))] internal partial class SerializeOnlyContext : JsonSerializerContext { }
個々の型の場合:
[JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext { }
完全なプログラムの例
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"} } } }
メタデータベース モード
コンテキスト全体の場合:
[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);
個々の型の場合:
[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);
完全なプログラムの例
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"} } } }
ASP.NET Core でのソース生成のサポート
Blazor アプリでは、ソース生成コンテキストまたは TypeInfo<TValue>
を受け取る HttpClientJsonExtensions.GetFromJsonAsync と HttpClientJsonExtensions.PostAsJsonAsync の拡張メソッドのオーバーロードを使います。
.NET 8 以降では、ソース生成コンテキストまたは TypeInfo<TValue>
を受け入れる HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable 拡張メソッドのオーバーロードも使用できます。
Razor Pages、MVC、SignalR、Web API アプリでは、JsonSerializerOptions.TypeInfoResolver プロパティを使ってコンテキストを指定します。
[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));
Note
JsonSourceGenerationMode.Serialization (高速パスのシリアル化) は、非同期シリアル化ではサポートされていません。
.NET 7 以前のバージョンでは、この制限は JsonSerializer.Serialize を受け入れる Stream の同期オーバーロードにも適用されます。 .NET 8 以降では、ストリーミングのシリアル化にはメタデータベースのモデルが必要ですが、ペイロードが事前に定義されたバッファー サイズに収まるほど小さいことがわかっている場合は、高速パスにフォールバックします。 詳細については、https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#jsonを参照してください。
リフレクションの既定値を無効にする
System.Text.Json では既定でリフレクションを使用するため、基本のシリアル化メソッドを呼び出すと、ネイティブ AOT アプリ (必要なすべてのリフレクション API がサポートされているわけではない) が中断される可能性があります。 これらの中断は、予測できないことがあり、アプリは多くの場合リフレクションが動作する CoreCLR ランタイムを使用してデバッグされるため、診断が難しい場合があります。 代わりに、リフレクションベースのシリアル化を明示的に無効にすると、中断の診断が簡単になります。 リフレクションベースのシリアル化を使用するコードでは、実行時に InvalidOperationException が発生し、説明メッセージがスローされます。
アプリで既定のリフレクションを無効にするには、プロジェクト ファイルで JsonSerializerIsReflectionEnabledByDefault
MSBuild プロパティを false
に設定します。
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
- このプロパティの動作は、ランタイム (CoreCLR またはネイティブ AOT) に関係なく一貫しています。
- このプロパティを指定せず、PublishTrimmed が有効になっている場合、リフレクションベースのシリアル化は自動的に無効になります。
JsonSerializer.IsReflectionEnabledByDefault プロパティを使用すると、リフレクションが無効になっているかどうかをプログラムで確認できます。 次のコード スニペットは、リフレクションが有効かどうかに応じてシリアライザーをどのように構成するかを示しています。
static JsonSerializerOptions CreateDefaultOptions()
{
return new()
{
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault
? new DefaultJsonTypeInfoResolver()
: MyContext.Default
};
}
このプロパティはリンク時定数として扱われるため、前のメソッドでは、ネイティブ AOT で実行されるアプリケーションにリフレクションベースのリゾルバーを固定しません。
オプションの指定
.NET 8 以降のバージョンでは、JsonSerializerOptions を使用して設定できるほとんどのオプションは、JsonSourceGenerationOptionsAttribute 属性を使用しても設定できます。 この属性を使用してオプションを設定する利点は、構成がコンパイル時に指定されることです。こうすると、生成された MyContext.Default
プロパティが、関連するすべてのオプションが設定された状態で事前に構成されます。
JsonSourceGenerationOptionsAttribute 属性を使用してオプションを設定する方法を次のコードに示します。
[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SerializationModeOptionsContext : JsonSerializerContext
{
}
JsonSourceGenerationOptionsAttribute
を使用してシリアル化オプションを指定する場合は、次のいずれかのシリアル化メソッドを呼び出します。
TypeInfo<TValue>
を受け取るJsonSerializer.Serialize
メソッド。 コンテキスト クラスのDefault.<TypeName>
プロパティを渡します。jsonString = JsonSerializer.Serialize( weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
コンテキストを受け取る
JsonSerializer.Serialize
メソッド。 コンテキスト クラスのDefault
の静的プロパティを渡します。jsonString = JsonSerializer.Serialize( weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default);
Utf8JsonWriter
の独自のインスタンスを渡すことができるメソッドを呼び出した場合、JsonSourceGenerationOptionsAttribute.WriteIndented
オプションの代わりにライターの Indented 設定が受け入れられます。
JsonSerializerOptions
インスタンスを取得するコンストラクターを呼び出すことで、コンテキスト インスタンスを作成および使用する場合は、JsonSourceGenerationOptionsAttribute
で指定されたオプションではなく、指定されたインスタンスが使用されます。
前の例の完全なプログラムを次に示します。
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"
//}
}
}
}
ソース ジェネレーターの結合
複数のソース生成コンテキストからのコントラクトを 1 つの JsonSerializerOptions インスタンス内で結合することができます。 JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]) メソッドを使って結合された複数のコンテキストを連結するには、JsonSerializerOptions.TypeInfoResolver プロパティを使います。
var options = new JsonSerializerOptions
{
TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default);
};
.NET 8 以降では、後で別のコンテキストを先頭または末尾に追加する場合は、JsonSerializerOptions.TypeInfoResolverChain プロパティを使って行うことができます。 連結の順序は重要です。JsonSerializerOptions は、各リゾルバーに指定された順序でクエリを実行し、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.
TypeInfoResolverChain プロパティに加えられた変更は TypeInfoResolver に反映され、その逆も同様です。
列挙型フィールドを文字列としてシリアル化する
既定では、列挙型は数値としてシリアル化されます。 ソース生成の使用時に特定の列挙型のフィールドを文字列としてシリアル化するには、JsonStringEnumConverter<TEnum> コンバーターを使用してそれに注釈を設定します。 または、すべての列挙に対して一括ポリシーを設定する場合は、JsonSourceGenerationOptionsAttribute 属性を使用します。
JsonStringEnumConverter<T>
コンバーター
ソース生成を使用して列挙型名を文字列としてシリアル化するには、 JsonStringEnumConverter<TEnum> コンバーターを使用します。 (非ジェネリック JsonStringEnumConverter 型は、ネイティブ AOT ランタイムではサポートされていません。)
JsonConverterAttribute 属性を使用して、JsonStringEnumConverter<TEnum> コンバーターで列挙型に注釈を設定します。
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
}
JsonSerializerContext クラスを作成し、 JsonSerializableAttribute 属性を使用して注釈を付けます:
[JsonSerializable(typeof(WeatherForecastWithPrecipEnum))]
public partial class Context1 : JsonSerializerContext { }
次のコードは、数値の代わりに列挙型名をシリアル化します:
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);
結果として生成される JSON は、次の例のようになります。
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Precipitation": "Sleet"
}
ブランケット ポリシー
JsonStringEnumConverter<TEnum> 型を使用する代わりに、JsonSourceGenerationOptionsAttributeを使用して列挙型を文字列としてシリアル化する一括ポリシーを適用できます。 以下のように JsonSerializerContext クラスを作成し、JsonSerializableAttribute "および" JsonSourceGenerationOptionsAttribute 属性を使用してそれに注釈を付けます。
[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
[JsonSerializable(typeof(WeatherForecast2WithPrecipEnum))]
public partial class Context2 : JsonSerializerContext { }
列挙型に 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
}
カスタム列挙型メンバー名
.NET 9 以降では、JsonStringEnumMemberName 属性を使用して列挙型メンバー名をカスタマイズできます。 詳細については、カスタム列挙メンバー名に関するページを参照してください。
関連項目
.NET