System.Text.Json
Library
System.Text.Json
Overview
System.Text.Json is Microsoft's official high-performance JSON library introduced in .NET Core 3.0. Positioned as the successor to Newtonsoft.Json, it emphasizes performance, security, and standards compliance in its design. With native UTF-8 support, source generation optimization, and thread-safe design, it has established itself as the recommended standard JSON library for new .NET applications in 2025.
Details
System.Text.Json 2025 edition achieves even greater performance improvements by leveraging .NET 8 features. Compared to Newtonsoft.Json, it achieves up to 40% faster processing and significant memory usage reduction, with particularly excellent performance in large dataset processing. It provides modern .NET development features as standard, including elimination of runtime metadata collection through C# source generation, UTF-8 encoding optimization, and application size reduction through trimming support.
Key Features
- High Performance: Up to 40% faster than Newtonsoft.Json with significant memory reduction
- UTF-8 Optimization: Efficient processing through native UTF-8 support
- Source Generation: Runtime performance improvement through compile-time optimization
- Thread Safe: Safe usage in multi-threaded environments
- Standards Compliance: Security and compatibility assurance through RFC compliance
- .NET Integration: Standard library in .NET Core/.NET 5+
Pros and Cons
Pros
- Overwhelming performance improvement compared to Newtonsoft.Json (up to 40% faster)
- Efficient memory usage and I/O processing through native UTF-8 support
- Application startup time reduction and memory reduction through source generation
- Safety in multi-threaded environments through thread-safe design
- No additional dependencies required as .NET standard library
- Application size reduction through Assembly trimming support
Cons
- Limited advanced features and flexibility compared to Newtonsoft.Json
- Insufficient custom functionality and converter support
- Learning cost required for migrating existing Newtonsoft.Json-based code
- Configuration changes may be needed due to default case-sensitive behavior
- Potential feature insufficiency for complex JSON schemas or conversion logic
- Not available in some .NET Framework environments
Reference Pages
- System.Text.Json Official Documentation
- Migrate from Newtonsoft.Json to System.Text.Json
- System.Text.Json Source Generation
Code Examples
Basic Setup
// Included by default in .NET Core 3.0+ / .NET 5+
using System.Text.Json;
using System.Text.Json.Serialization;
// When using as NuGet package (.NET Framework, etc.)
// Install-Package System.Text.Json
<!-- PackageReference for .NET Framework -->
<PackageReference Include="System.Text.Json" Version="8.0.4" />
Basic Serialization and Deserialization
using System.Text.Json;
using System.Text.Json.Serialization;
// Basic 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()
{
// Object creation
var user = new User
{
Id = 123,
Name = "John Doe",
Email = "[email protected]",
IsActive = true,
CreatedAt = DateTime.Now,
Tags = new List<string> { "admin", "premium" }
};
// 1. Object → JSON string
string jsonString = JsonSerializer.Serialize(user);
Console.WriteLine($"JSON string: {jsonString}");
// 2. Object → UTF-8 byte array
byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(user);
Console.WriteLine($"UTF-8 byte count: {jsonUtf8Bytes.Length}");
// 3. Object → File (async)
await using var fileStream = File.Create("user.json");
await JsonSerializer.SerializeAsync(fileStream, user);
// 4. JSON string → Object
string jsonInput = """{"Id":456,"Name":"Jane Smith","Email":"[email protected]","IsActive":true,"CreatedAt":"2025-01-01T12:00:00","Tags":["user"]}""";
User? deserializedUser = JsonSerializer.Deserialize<User>(jsonInput);
Console.WriteLine($"Deserialized: {deserializedUser?.Name}");
// 5. UTF-8 byte array → Object
User? userFromBytes = JsonSerializer.Deserialize<User>(jsonUtf8Bytes);
Console.WriteLine($"From bytes: {userFromBytes?.Name}");
// 6. File → Object (async)
await using var readStream = File.OpenRead("user.json");
User? userFromFile = await JsonSerializer.DeserializeAsync<User>(readStream);
Console.WriteLine($"From file: {userFromFile?.Name}");
// 7. Stream processing
using var memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, user);
memoryStream.Position = 0;
User? userFromStream = await JsonSerializer.DeserializeAsync<User>(memoryStream);
Console.WriteLine($"From stream: {userFromStream?.Name}");
}
// Generic types and collections processing
public static void GenericCollections()
{
// List<T> serialization
var users = new List<User>
{
new User { Id = 1, Name = "John", Email = "[email protected]", IsActive = true, CreatedAt = DateTime.Now },
new User { Id = 2, Name = "Jane", Email = "[email protected]", IsActive = false, CreatedAt = DateTime.Now }
};
string userListJson = JsonSerializer.Serialize(users);
List<User>? deserializedUsers = JsonSerializer.Deserialize<List<User>>(userListJson);
Console.WriteLine($"User count: {deserializedUsers?.Count}");
// Dictionary<TKey, TValue> serialization
var scoreMap = new Dictionary<string, int>
{
["English"] = 85,
["Math"] = 92,
["Science"] = 78
};
string mapJson = JsonSerializer.Serialize(scoreMap);
Dictionary<string, int>? deserializedMap = JsonSerializer.Deserialize<Dictionary<string, int>>(mapJson);
Console.WriteLine($"Subject count: {deserializedMap?.Count}");
// Complex nested types
var departmentData = new Dictionary<string, List<User>>
{
["Engineering"] = users.Where(u => u.Id == 1).ToList(),
["Sales"] = users.Where(u => u.Id == 2).ToList()
};
string complexJson = JsonSerializer.Serialize(departmentData);
var deserializedComplex = JsonSerializer.Deserialize<Dictionary<string, List<User>>>(complexJson);
Console.WriteLine($"Department count: {deserializedComplex?.Count}");
}
}
JsonSerializerOptions Detailed Configuration
using System.Text.Json;
using System.Text.Json.Serialization;
public class ConfigurationExamples
{
public static JsonSerializerOptions CreateConfiguredOptions()
{
var options = new JsonSerializerOptions
{
// JSON output formatting
WriteIndented = true,
// Property naming policy
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// Enum handling
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
},
// Ignore unknown properties
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
// Default value handling
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
// Allow reading numbers from strings
NumberHandling = JsonNumberHandling.AllowReadingFromString,
// Encoder settings (for non-ASCII characters)
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// Include read-only properties
IncludeFields = false,
// Case-insensitive comparison
PropertyNameCaseInsensitive = true,
// Allow comments and trailing commas
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
return options;
}
public static void DemonstrateConfiguration()
{
var options = CreateConfiguredOptions();
var data = new
{
UserId = 123,
UserName = "John Doe",
UserEmail = "[email protected]",
CreatedAt = DateTime.Now,
Status = UserStatus.Active,
Score = 0, // Default value
Description = (string?)null // Null value
};
// Serialization with applied configuration
string json = JsonSerializer.Serialize(data, options);
Console.WriteLine($"Configured JSON:\n{json}");
// Flexible JSON parsing
string flexibleJson = """
{
// This is a comment
"userId": "456", // Number specified as string
"userName": "Jane Smith",
"userEmail": "[email protected]",
"status": "inactive", // Lowercase enum
"score": 95,
"unknownField": "This will be ignored" // Unmapped field
}
""";
var parsed = JsonSerializer.Deserialize<User>(flexibleJson, options);
Console.WriteLine($"Flexible parsing result: {parsed?.Name}");
}
public enum UserStatus
{
Active,
Inactive,
Pending
}
}
Custom Attributes and Converters
using System.Text.Json;
using System.Text.Json.Serialization;
// Advanced POCO using attributes
public class AdvancedUser
{
[JsonPropertyName("user_id")]
public int Id { get; set; }
[JsonPropertyName("full_name")]
public string Name { get; set; } = string.Empty;
[JsonIgnore] // Ignore in serialization
public string Password { get; set; } = string.Empty;
[JsonInclude] // Include private field
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() { }
}
// Custom DateTime converter
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"));
}
}
// Immutable struct (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()
{
// Using custom attributes
var user = new AdvancedUser(1, "Advanced User")
{
Description = "This is a description",
Tags = new List<string> { "advanced", "test" }
};
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(user, options);
Console.WriteLine($"Advanced serialization:\n{json}");
// JSON with extension data
string jsonWithExtensions = """
{
"user_id": 2,
"full_name": "Extension User",
"created_at": "2025-01-01 12:00:00",
"extra_field1": "Additional data 1",
"extra_field2": 42,
"extra_field3": {
"nested": "Nested data"
}
}
""";
AdvancedUser? userWithExtensions = JsonSerializer.Deserialize<AdvancedUser>(jsonWithExtensions, options);
Console.WriteLine($"Extension data: {userWithExtensions?.ExtensionData?.Count} fields");
// Record serialization
var person = new ImmutablePerson(123, "John", "[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}");
}
}
Source Generation Optimization
using System.Text.Json;
using System.Text.Json.Serialization;
// Source generation context definition
[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
{
}
// Performance test data
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 = "Source Generation Test",
Timestamp = DateTime.UtcNow,
Value = 123.45,
Tags = new List<string> { "performance", "source-gen" },
Metadata = new Dictionary<string, object>
{
["category"] = "test",
["priority"] = 1
}
};
// Serialization using source generation
string json = JsonSerializer.Serialize(testData, MyJsonContext.Default.PerformanceTestData);
Console.WriteLine($"Source generation JSON:\n{json}");
// Deserialization using source generation
PerformanceTestData? deserialized = JsonSerializer.Deserialize(json, MyJsonContext.Default.PerformanceTestData);
Console.WriteLine($"Source generation deserialize: {deserialized?.Name}");
// Source generation with lists
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($"Source generation list: {deserializedList?.Count} items");
}
// Performance comparison
public static async Task PerformanceBenchmark()
{
const int iterations = 100_000;
var testData = GenerateTestData(1000);
// Normal serialization
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
_ = JsonSerializer.Serialize(testData);
}
stopwatch.Stop();
long normalTime = stopwatch.ElapsedMilliseconds;
// Source generation serialization
stopwatch.Restart();
for (int i = 0; i < iterations; i++)
{
_ = JsonSerializer.Serialize(testData, MyJsonContext.Default.ListPerformanceTestData);
}
stopwatch.Stop();
long sourceGenTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Normal serialization: {normalTime}ms");
Console.WriteLine($"Source generation: {sourceGenTime}ms");
Console.WriteLine($"Performance improvement: {(double)normalTime / sourceGenTime:F2}x");
// Memory usage measurement
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($"Memory usage: {(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;
}
}
Streaming and Utf8JsonReader Utilization
using System.Text.Json;
using System.Buffers;
public class StreamingJsonExample
{
// Streaming read of large JSON files
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;
}
// High-performance reading with 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;
}
// High-performance writing with 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();
}
// Asynchronous streaming processing
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)
{
// Simulate asynchronous processing
await Task.Delay(1);
yield return user;
}
}
}
}
public static async Task DemonstrateStreamingOperations()
{
// Generate test data
var users = Enumerable.Range(1, 10000)
.Select(i => new User
{
Id = i,
Name = $"User {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();
// High-speed writing with Utf8JsonWriter
byte[] jsonBytes = CreateJsonWithUtf8Writer(users);
Console.WriteLine($"Generated JSON size: {jsonBytes.Length / 1024} KB");
// High-speed reading with Utf8JsonReader
var extractedNames = ExtractNamesWithUtf8Reader(jsonBytes);
Console.WriteLine($"Extracted name count: {extractedNames.Count}");
// Asynchronous streaming processing
using var memoryStream = new MemoryStream(jsonBytes);
int processedCount = 0;
await foreach (var user in ProcessUsersStreamAsync(memoryStream))
{
processedCount++;
if (processedCount % 1000 == 0)
{
Console.WriteLine($"Processed: {processedCount} users");
}
}
Console.WriteLine($"Total processed: {processedCount}");
}
}
Error Handling and Best Practices
using System.Text.Json;
public class ErrorHandlingExamples
{
public static void ComprehensiveErrorHandling()
{
// 1. Basic error handling
try
{
string invalidJson = "{\"name\": John Doe, \"age\": }"; // Invalid JSON
_ = JsonSerializer.Deserialize<User>(invalidJson);
}
catch (JsonException ex)
{
Console.WriteLine($"JSON format error: {ex.Message}");
Console.WriteLine($"Position: Line {ex.LineNumber}, Position {ex.BytePositionInLine}");
}
// 2. Type conversion error handling
try
{
string typeError = """{"id": "not-a-number", "name": "Test"}""";
_ = JsonSerializer.Deserialize<User>(typeError);
}
catch (JsonException ex)
{
Console.WriteLine($"Type conversion error: {ex.Message}");
}
// 3. Safe deserialization
static T? SafeDeserialize<T>(string json) where T : class
{
try
{
return JsonSerializer.Deserialize<T>(json);
}
catch (JsonException ex)
{
Console.WriteLine($"Deserialization failed: {ex.Message}");
return null;
}
catch (ArgumentNullException)
{
Console.WriteLine("JSON is null");
return null;
}
catch (NotSupportedException ex)
{
Console.WriteLine($"Unsupported operation: {ex.Message}");
return null;
}
}
// 4. Partial JSON parsing
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($"Error during partial parsing: {ex.Message}");
}
return result;
}
// 5. Custom error handling
var options = new JsonSerializerOptions
{
// Skip unknown properties
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
// Allow default values
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
// Case-insensitive
PropertyNameCaseInsensitive = true,
// Allow reading numbers from strings
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
// Robust deserialization example
string robustJson = """
{
"ID": "123", // Number as string
"Name": "Robust User",
"email": "[email protected]", // Lowercase
"IsActive": true,
"UnknownField": "This will be ignored", // Unknown field
"createdAt": "2025-01-01T12:00:00Z"
}
""";
User? robustUser = SafeDeserialize<User>(robustJson);
if (robustUser != null)
{
Console.WriteLine($"Robust parsing success: {robustUser.Name}");
}
// 6. Performance monitoring
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($"Deserialization time: {stopwatch.ElapsedMilliseconds}ms");
Console.WriteLine($"Memory usage: {memoryUsed} bytes");
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
Console.WriteLine($"Error (elapsed time: {stopwatch.ElapsedMilliseconds}ms): {ex.Message}");
return null;
}
}
// Usage example
string testJson = JsonSerializer.Serialize(new User
{
Id = 1,
Name = "Metrics Test",
Email = "[email protected]",
IsActive = true,
CreatedAt = DateTime.Now
});
_ = DeserializeWithMetrics<User>(testJson);
}
}