System.Text.Json
ライブラリ
System.Text.Json
概要
System.Text.Jsonは、.NET Core 3.0で導入されたMicrosoft公式の高性能JSONライブラリです。Newtonsoft.Jsonの後継として位置づけられ、パフォーマンス、セキュリティ、標準準拠を重視した設計となっています。UTF-8ネイティブサポート、ソース生成による最適化、スレッドセーフ設計により、2025年の新しい.NETアプリケーションで推奨される標準JSONライブラリとして確立されています。
詳細
System.Text.Json 2025年版は、.NET 8の機能を活用してさらなる性能向上を実現しています。Newtonsoft.Jsonと比較して最大40%の高速化と大幅なメモリ使用量削減を達成し、大規模データセットでの処理において特に優秀な性能を発揮します。C#ソース生成機能による実行時メタデータ収集の排除、UTF-8エンコーディングの最適化、trimming対応によるアプリサイズ削減など、モダンな.NET開発に必要な機能を標準で提供します。
主な特徴
- 高性能: Newtonsoft.Jsonより最大40%高速で大幅なメモリ削減
- UTF-8最適化: ネイティブUTF-8サポートによる効率的な処理
- ソース生成: コンパイル時最適化によるランタイム性能向上
- スレッドセーフ: マルチスレッド環境での安全な使用
- 標準準拠: RFC準拠によるセキュリティと互換性の確保
- .NET統合: .NET Core/.NET 5+での標準ライブラリ
メリット・デメリット
メリット
- Newtonsoft.Jsonと比較して圧倒的な性能向上(最大40%高速化)
- UTF-8ネイティブサポートによる効率的なメモリ使用とI/O処理
- ソース生成によるアプリケーション起動時間短縮とメモリ削減
- スレッドセーフ設計によるマルチスレッド環境での安全性
- .NET標準ライブラリのため追加依存関係が不要
- Assembly trimmingサポートによるアプリケーションサイズ削減
デメリット
- Newtonsoft.Jsonと比較して高度な機能や柔軟性が限定的
- 一部のカスタマイゼーション機能やコンバーターサポートが不十分
- 既存のNewtonsoft.Jsonベースコードの移行に学習コストが必要
- デフォルトで大文字小文字を区別するため設定変更が必要な場合がある
- 複雑なJSONスキーマや変換ロジックには機能不足の可能性
- 一部の.NET Framework環境では利用できない
参考ページ
書き方の例
基本的なセットアップ
// .NET Core 3.0+ / .NET 5+ では標準で含まれています
using System.Text.Json;
using System.Text.Json.Serialization;
// NuGet パッケージとして使用する場合(.NET Framework等)
// Install-Package System.Text.Json
<!-- .NET Framework での PackageReference -->
<PackageReference Include="System.Text.Json" Version="8.0.4" />
基本的なシリアライゼーション・デシリアライゼーション
using System.Text.Json;
using System.Text.Json.Serialization;
// 基本的なPOCO
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public List<string> Tags { get; set; } = new List<string>();
}
public class BasicSerialization
{
public static void DemonstrateBasicOperations()
{
// オブジェクトの作成
var user = new User
{
Id = 123,
Name = "田中太郎",
Email = "[email protected]",
IsActive = true,
CreatedAt = DateTime.Now,
Tags = new List<string> { "admin", "premium" }
};
// 1. オブジェクト → JSON文字列
string jsonString = JsonSerializer.Serialize(user);
Console.WriteLine($"JSON文字列: {jsonString}");
// 2. オブジェクト → UTF-8バイト配列
byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(user);
Console.WriteLine($"UTF-8バイト数: {jsonUtf8Bytes.Length}");
// 3. オブジェクト → ファイル(非同期)
await using var fileStream = File.Create("user.json");
await JsonSerializer.SerializeAsync(fileStream, user);
// 4. JSON文字列 → オブジェクト
string jsonInput = """{"Id":456,"Name":"佐藤花子","Email":"[email protected]","IsActive":true,"CreatedAt":"2025-01-01T12:00:00","Tags":["user"]}""";
User? deserializedUser = JsonSerializer.Deserialize<User>(jsonInput);
Console.WriteLine($"デシリアライズ: {deserializedUser?.Name}");
// 5. UTF-8バイト配列 → オブジェクト
User? userFromBytes = JsonSerializer.Deserialize<User>(jsonUtf8Bytes);
Console.WriteLine($"バイトから: {userFromBytes?.Name}");
// 6. ファイル → オブジェクト(非同期)
await using var readStream = File.OpenRead("user.json");
User? userFromFile = await JsonSerializer.DeserializeAsync<User>(readStream);
Console.WriteLine($"ファイルから: {userFromFile?.Name}");
// 7. ストリーム処理
using var memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, user);
memoryStream.Position = 0;
User? userFromStream = await JsonSerializer.DeserializeAsync<User>(memoryStream);
Console.WriteLine($"ストリームから: {userFromStream?.Name}");
}
// ジェネリック型とコレクションの処理
public static void GenericCollections()
{
// List<T>のシリアライゼーション
var users = new List<User>
{
new User { Id = 1, Name = "太郎", Email = "[email protected]", IsActive = true, CreatedAt = DateTime.Now },
new User { Id = 2, Name = "花子", Email = "[email protected]", IsActive = false, CreatedAt = DateTime.Now }
};
string userListJson = JsonSerializer.Serialize(users);
List<User>? deserializedUsers = JsonSerializer.Deserialize<List<User>>(userListJson);
Console.WriteLine($"ユーザー数: {deserializedUsers?.Count}");
// Dictionary<TKey, TValue>のシリアライゼーション
var scoreMap = new Dictionary<string, int>
{
["国語"] = 85,
["数学"] = 92,
["英語"] = 78
};
string mapJson = JsonSerializer.Serialize(scoreMap);
Dictionary<string, int>? deserializedMap = JsonSerializer.Deserialize<Dictionary<string, int>>(mapJson);
Console.WriteLine($"科目数: {deserializedMap?.Count}");
// 複雑なネストした型
var departmentData = new Dictionary<string, List<User>>
{
["開発部"] = users.Where(u => u.Id == 1).ToList(),
["営業部"] = users.Where(u => u.Id == 2).ToList()
};
string complexJson = JsonSerializer.Serialize(departmentData);
var deserializedComplex = JsonSerializer.Deserialize<Dictionary<string, List<User>>>(complexJson);
Console.WriteLine($"部署数: {deserializedComplex?.Count}");
}
}
JsonSerializerOptionsによる詳細設定
using System.Text.Json;
using System.Text.Json.Serialization;
public class ConfigurationExamples
{
public static JsonSerializerOptions CreateConfiguredOptions()
{
var options = new JsonSerializerOptions
{
// JSON出力の整形
WriteIndented = true,
// プロパティ名の命名規則
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 列挙型の処理
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
},
// 未知のプロパティの無視
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
// デフォルト値の扱い
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
// 数値の文字列化を許可
NumberHandling = JsonNumberHandling.AllowReadingFromString,
// エンコーダー設定(日本語文字対応)
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// 読み取り専用プロパティを含める
IncludeFields = false,
// 大文字小文字を区別しない比較
PropertyNameCaseInsensitive = true,
// コメントとトレーリングカンマを許可
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
return options;
}
public static void DemonstrateConfiguration()
{
var options = CreateConfiguredOptions();
var data = new
{
UserId = 123,
UserName = "田中太郎",
UserEmail = "[email protected]",
CreatedAt = DateTime.Now,
Status = UserStatus.Active,
Score = 0, // デフォルト値
Description = (string?)null // null値
};
// 設定適用済みのシリアライゼーション
string json = JsonSerializer.Serialize(data, options);
Console.WriteLine($"設定済みJSON:\n{json}");
// 柔軟なJSONの解析
string flexibleJson = """
{
// これはコメントです
"userId": "456", // 文字列として指定された数値
"userName": "佐藤花子",
"userEmail": "[email protected]",
"status": "inactive", // 小文字の列挙型
"score": 95,
"unknownField": "これは無視されます" // マッピングされていないフィールド
}
""";
var parsed = JsonSerializer.Deserialize<User>(flexibleJson, options);
Console.WriteLine($"柔軟解析結果: {parsed?.Name}");
}
public enum UserStatus
{
Active,
Inactive,
Pending
}
}
カスタム属性とコンバーター
using System.Text.Json;
using System.Text.Json.Serialization;
// 属性を使用した高度なPOCO
public class AdvancedUser
{
[JsonPropertyName("user_id")]
public int Id { get; set; }
[JsonPropertyName("full_name")]
public string Name { get; set; } = string.Empty;
[JsonIgnore] // シリアライゼーションで無視
public string Password { get; set; } = string.Empty;
[JsonInclude] // プライベートフィールドを含める
private string _secretToken = "token123";
[JsonPropertyName("created_at")]
[JsonConverter(typeof(CustomDateTimeConverter))]
public DateTime CreatedAt { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Description { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public List<string> Tags { get; set; } = new List<string>();
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
[JsonConstructor]
public AdvancedUser(int id, string name)
{
Id = id;
Name = name;
CreatedAt = DateTime.UtcNow;
}
public AdvancedUser() { }
}
// カスタムDateTimeコンバーター
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is string dateString)
{
if (DateTime.TryParseExact(dateString, "yyyy-MM-dd HH:mm:ss", null, DateTimeStyles.None, out DateTime result))
{
return result;
}
}
throw new JsonException("Invalid date format");
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
// イミュータブルな構造体(record)
public record ImmutablePerson(
[property: JsonPropertyName("person_id")] int Id,
[property: JsonPropertyName("person_name")] string Name,
[property: JsonPropertyName("person_email")] string Email
)
{
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
[JsonIgnore]
public string DisplayName => $"{Name} ({Email})";
}
public class AdvancedFeatures
{
public static void DemonstrateAdvancedFeatures()
{
// カスタム属性の使用
var user = new AdvancedUser(1, "高度ユーザー")
{
Description = "これは説明です",
Tags = new List<string> { "advanced", "test" }
};
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(user, options);
Console.WriteLine($"高度なシリアライゼーション:\n{json}");
// 拡張データを含むJSON
string jsonWithExtensions = """
{
"user_id": 2,
"full_name": "拡張ユーザー",
"created_at": "2025-01-01 12:00:00",
"extra_field1": "追加データ1",
"extra_field2": 42,
"extra_field3": {
"nested": "ネストしたデータ"
}
}
""";
AdvancedUser? userWithExtensions = JsonSerializer.Deserialize<AdvancedUser>(jsonWithExtensions, options);
Console.WriteLine($"拡張データ: {userWithExtensions?.ExtensionData?.Count} フィールド");
// recordのシリアライゼーション
var person = new ImmutablePerson(123, "太郎", "[email protected]");
string recordJson = JsonSerializer.Serialize(person, options);
Console.WriteLine($"Record JSON:\n{recordJson}");
ImmutablePerson? deserializedPerson = JsonSerializer.Deserialize<ImmutablePerson>(recordJson, options);
Console.WriteLine($"Record: {deserializedPerson?.DisplayName}");
}
}
ソース生成による最適化
using System.Text.Json;
using System.Text.Json.Serialization;
// ソース生成コンテキストの定義
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
[JsonSerializable(typeof(Dictionary<string, User>))]
[JsonSerializable(typeof(PerformanceTestData))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
)]
public partial class MyJsonContext : JsonSerializerContext
{
}
// 高性能テスト用データ
public class PerformanceTestData
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public double Value { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
}
public class SourceGenerationExample
{
public static void DemonstrateSourceGeneration()
{
var testData = new PerformanceTestData
{
Id = 1,
Name = "ソース生成テスト",
Timestamp = DateTime.UtcNow,
Value = 123.45,
Tags = new List<string> { "performance", "source-gen" },
Metadata = new Dictionary<string, object>
{
["category"] = "test",
["priority"] = 1
}
};
// ソース生成を使用したシリアライゼーション
string json = JsonSerializer.Serialize(testData, MyJsonContext.Default.PerformanceTestData);
Console.WriteLine($"ソース生成JSON:\n{json}");
// ソース生成を使用したデシリアライゼーション
PerformanceTestData? deserialized = JsonSerializer.Deserialize(json, MyJsonContext.Default.PerformanceTestData);
Console.WriteLine($"ソース生成デシリアライズ: {deserialized?.Name}");
// リストでのソース生成
var dataList = new List<PerformanceTestData> { testData };
string listJson = JsonSerializer.Serialize(dataList, MyJsonContext.Default.ListPerformanceTestData);
List<PerformanceTestData>? deserializedList = JsonSerializer.Deserialize(listJson, MyJsonContext.Default.ListPerformanceTestData);
Console.WriteLine($"ソース生成リスト: {deserializedList?.Count} アイテム");
}
// パフォーマンス比較
public static async Task PerformanceBenchmark()
{
const int iterations = 100_000;
var testData = GenerateTestData(1000);
// 通常のシリアライゼーション
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
_ = JsonSerializer.Serialize(testData);
}
stopwatch.Stop();
long normalTime = stopwatch.ElapsedMilliseconds;
// ソース生成シリアライゼーション
stopwatch.Restart();
for (int i = 0; i < iterations; i++)
{
_ = JsonSerializer.Serialize(testData, MyJsonContext.Default.ListPerformanceTestData);
}
stopwatch.Stop();
long sourceGenTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"通常のシリアライゼーション: {normalTime}ms");
Console.WriteLine($"ソース生成: {sourceGenTime}ms");
Console.WriteLine($"性能向上: {(double)normalTime / sourceGenTime:F2}x");
// メモリ使用量の測定
long memoryBefore = GC.GetTotalMemory(true);
for (int i = 0; i < 10_000; i++)
{
_ = JsonSerializer.Serialize(testData, MyJsonContext.Default.ListPerformanceTestData);
}
long memoryAfter = GC.GetTotalMemory(false);
Console.WriteLine($"メモリ使用量: {(memoryAfter - memoryBefore) / 1024 / 1024} MB");
}
private static List<PerformanceTestData> GenerateTestData(int count)
{
var data = new List<PerformanceTestData>();
for (int i = 0; i < count; i++)
{
data.Add(new PerformanceTestData
{
Id = i,
Name = $"Item {i}",
Timestamp = DateTime.UtcNow.AddSeconds(i),
Value = i * 1.5,
Tags = new List<string> { $"tag{i % 3}", $"category{i % 5}" },
Metadata = new Dictionary<string, object>
{
["index"] = i,
["active"] = i % 2 == 0
}
});
}
return data;
}
}
ストリーミングとUtf8JsonReaderの活用
using System.Text.Json;
using System.Buffers;
public class StreamingJsonExample
{
// 大容量JSONファイルのストリーミング読み込み
public static async Task<List<User>> ReadLargeJsonFileAsync(string filePath)
{
var users = new List<User>();
await using var fileStream = File.OpenRead(filePath);
using var jsonDoc = await JsonDocument.ParseAsync(fileStream);
if (jsonDoc.RootElement.TryGetProperty("users", out JsonElement usersElement) &&
usersElement.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement userElement in usersElement.EnumerateArray())
{
var user = JsonSerializer.Deserialize<User>(userElement.GetRawText());
if (user != null)
{
users.Add(user);
}
}
}
return users;
}
// Utf8JsonReaderによる高性能読み込み
public static List<string> ExtractNamesWithUtf8Reader(ReadOnlySpan<byte> jsonData)
{
var names = new List<string>();
var reader = new Utf8JsonReader(jsonData);
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString() ?? string.Empty;
if (propertyName.Equals("name", StringComparison.OrdinalIgnoreCase))
{
if (reader.Read() && reader.TokenType == JsonTokenType.String)
{
string? name = reader.GetString();
if (name != null)
{
names.Add(name);
}
}
}
}
}
return names;
}
// Utf8JsonWriterによる高性能書き込み
public static byte[] CreateJsonWithUtf8Writer(IEnumerable<User> users)
{
using var buffer = new ArrayBufferWriter<byte>();
using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = true });
writer.WriteStartObject();
writer.WriteStartArray("users");
foreach (var user in users)
{
writer.WriteStartObject();
writer.WriteNumber("id", user.Id);
writer.WriteString("name", user.Name);
writer.WriteString("email", user.Email);
writer.WriteBoolean("isActive", user.IsActive);
writer.WriteString("createdAt", user.CreatedAt.ToString("O"));
writer.WriteStartArray("tags");
foreach (var tag in user.Tags)
{
writer.WriteStringValue(tag);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
return buffer.WrittenSpan.ToArray();
}
// 非同期ストリーミング処理
public static async IAsyncEnumerable<User> ProcessUsersStreamAsync(Stream jsonStream)
{
using var jsonDoc = await JsonDocument.ParseAsync(jsonStream);
if (jsonDoc.RootElement.TryGetProperty("users", out JsonElement usersElement))
{
foreach (JsonElement userElement in usersElement.EnumerateArray())
{
var user = JsonSerializer.Deserialize<User>(userElement.GetRawText());
if (user != null)
{
// 非同期処理のシミュレーション
await Task.Delay(1);
yield return user;
}
}
}
}
public static async Task DemonstrateStreamingOperations()
{
// テストデータの生成
var users = Enumerable.Range(1, 10000)
.Select(i => new User
{
Id = i,
Name = $"ユーザー {i}",
Email = $"user{i}@example.com",
IsActive = i % 10 != 0,
CreatedAt = DateTime.UtcNow.AddDays(-i),
Tags = new List<string> { $"tag{i % 5}", $"category{i % 3}" }
})
.ToList();
// Utf8JsonWriterによる高速書き込み
byte[] jsonBytes = CreateJsonWithUtf8Writer(users);
Console.WriteLine($"生成されたJSONサイズ: {jsonBytes.Length / 1024} KB");
// Utf8JsonReaderによる高速読み込み
var extractedNames = ExtractNamesWithUtf8Reader(jsonBytes);
Console.WriteLine($"抽出された名前の数: {extractedNames.Count}");
// 非同期ストリーミング処理
using var memoryStream = new MemoryStream(jsonBytes);
int processedCount = 0;
await foreach (var user in ProcessUsersStreamAsync(memoryStream))
{
processedCount++;
if (processedCount % 1000 == 0)
{
Console.WriteLine($"処理済み: {processedCount} ユーザー");
}
}
Console.WriteLine($"合計処理数: {processedCount}");
}
}
エラーハンドリングとベストプラクティス
using System.Text.Json;
public class ErrorHandlingExamples
{
public static void ComprehensiveErrorHandling()
{
// 1. 基本的なエラーハンドリング
try
{
string invalidJson = "{\"name\": 田中太郎, \"age\": }"; // 不正なJSON
_ = JsonSerializer.Deserialize<User>(invalidJson);
}
catch (JsonException ex)
{
Console.WriteLine($"JSON形式エラー: {ex.Message}");
Console.WriteLine($"位置: Line {ex.LineNumber}, Position {ex.BytePositionInLine}");
}
// 2. 型変換エラーの処理
try
{
string typeError = """{"id": "not-a-number", "name": "テスト"}""";
_ = JsonSerializer.Deserialize<User>(typeError);
}
catch (JsonException ex)
{
Console.WriteLine($"型変換エラー: {ex.Message}");
}
// 3. 安全なデシリアライゼーション
static T? SafeDeserialize<T>(string json) where T : class
{
try
{
return JsonSerializer.Deserialize<T>(json);
}
catch (JsonException ex)
{
Console.WriteLine($"デシリアライゼーション失敗: {ex.Message}");
return null;
}
catch (ArgumentNullException)
{
Console.WriteLine("JSONが null です");
return null;
}
catch (NotSupportedException ex)
{
Console.WriteLine($"サポートされていない操作: {ex.Message}");
return null;
}
}
// 4. 部分的なJSONパース
static Dictionary<string, object?> ParsePartialJson(string json)
{
var result = new Dictionary<string, object?>();
try
{
using var doc = JsonDocument.Parse(json);
foreach (var property in doc.RootElement.EnumerateObject())
{
result[property.Name] = property.Value.ValueKind switch
{
JsonValueKind.String => property.Value.GetString(),
JsonValueKind.Number => property.Value.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
_ => property.Value.ToString()
};
}
}
catch (JsonException ex)
{
Console.WriteLine($"部分パース中にエラー: {ex.Message}");
}
return result;
}
// 5. カスタムエラーハンドリング
var options = new JsonSerializerOptions
{
// 不明プロパティをスキップ
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
// デフォルト値を許可
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
// 大文字小文字を区別しない
PropertyNameCaseInsensitive = true,
// 数値の文字列読み取りを許可
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
// 堅牢なデシリアライゼーション例
string robustJson = """
{
"ID": "123", // 数値を文字列として
"Name": "ロバストユーザー",
"email": "[email protected]", // 小文字
"IsActive": true,
"UnknownField": "これは無視される", // 不明フィールド
"createdAt": "2025-01-01T12:00:00Z"
}
""";
User? robustUser = SafeDeserialize<User>(robustJson);
if (robustUser != null)
{
Console.WriteLine($"堅牢なパース成功: {robustUser.Name}");
}
// 6. パフォーマンス監視
static async Task<T?> DeserializeWithMetrics<T>(string json) where T : class
{
var stopwatch = Stopwatch.StartNew();
long memoryBefore = GC.GetTotalMemory(false);
try
{
T? result = JsonSerializer.Deserialize<T>(json);
stopwatch.Stop();
long memoryAfter = GC.GetTotalMemory(false);
long memoryUsed = memoryAfter - memoryBefore;
Console.WriteLine($"デシリアライゼーション時間: {stopwatch.ElapsedMilliseconds}ms");
Console.WriteLine($"メモリ使用量: {memoryUsed} bytes");
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
Console.WriteLine($"エラー (経過時間: {stopwatch.ElapsedMilliseconds}ms): {ex.Message}");
return null;
}
}
// 使用例
string testJson = JsonSerializer.Serialize(new User
{
Id = 1,
Name = "メトリクステスト",
Email = "[email protected]",
IsActive = true,
CreatedAt = DateTime.Now
});
_ = DeserializeWithMetrics<User>(testJson);
}
}