JSON to najpopularniejszy format wymiany danych! W 2015 roku używałeś Newtonsoft.Json (Json.NET). W 2026 roku standard to System.Text.Json - szybszy, built-in w .NET, z Source Generators dla ultra-wydajności!
Performance - 2-3x szybsze niż Newtonsoft.Json Memory - mniej alokacji Built-in - w .NET, nie trzeba NuGet Modern - Span<T>, Source Generators Security - lepsze zabezpieczenia
System.Text.Json - podstawy
Serialization - obiekt → JSON
using System.Text.Json;
// Klasa do serializacji
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
// Serialize object → JSON string
var person = new Person
{
Name = "Jan Kowalski",
Age = 30,
Email = "jan@example.com"
};
string json = JsonSerializer.Serialize(person);
Console.WriteLine(json);
// {"Name":"Jan Kowalski","Age":30,"Email":"jan@example.com"}
// Serialize z formatowaniem (pretty print)
var options = new JsonSerializerOptions { WriteIndented = true };
string prettyJson = JsonSerializer.Serialize(person, options);
Console.WriteLine(prettyJson);
// {
// "Name": "Jan Kowalski",
// "Age": 30,
// "Email": "jan@example.com"
// }
Deserialization - JSON → obiekt
// Deserialize JSON string → object
string json = "{\"Name\":\"Anna Nowak\",\"Age\":25,\"Email\":\"anna@example.com\"}";
Person person = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"{person.Name}, {person.Age}, {person.Email}");
// Anna Nowak, 25, anna@example.com
// Deserialize z file
string fileJson = File.ReadAllText("person.json");
Person personFromFile = JsonSerializer.Deserialize<Person>(fileJson);
// Deserialize async z stream
await using var stream = File.OpenRead("person.json");
Person personFromStream = await JsonSerializer.DeserializeAsync<Person>(stream);
using System.Text.Json.Serialization;
public class Person
{
// Custom property name w JSON
[JsonPropertyName("full_name")]
public string Name { get; set; }
public int Age { get; set; }
// Ignore property (nie serializuj)
[JsonIgnore]
public string Password { get; set; }
// Ignore jeśli null
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string MiddleName { get; set; }
// Kolejność properties w JSON
[JsonPropertyOrder(1)]
public string Email { get; set; }
// Include field (domyślnie fields są ignorowane)
[JsonInclude]
public string _internalId;
}
var person = new Person
{
Name = "Jan",
Age = 30,
Password = "secret123",
Email = "jan@example.com",
_internalId = "ABC123"
};
string json = JsonSerializer.Serialize(person);
// {
// "full_name": "Jan", ← custom name
// "Age": 30,
// "Email": "jan@example.com",
// "_internalId": "ABC123" ← included field
// }
// Password nie ma (JsonIgnore) ✨
Advanced Serialization
Collections i nested objects
// Collections
var numbers = new List<int> { 1, 2, 3, 4, 5 };
string json1 = JsonSerializer.Serialize(numbers);
// [1,2,3,4,5]
var dict = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3
};
string json2 = JsonSerializer.Serialize(dict);
// {"one":1,"two":2,"three":3}
// Nested objects
public class Company
{
public string Name { get; set; }
public Address Address { get; set; }
public List<Employee> Employees { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
public class Employee
{
public string Name { get; set; }
public string Position { get; set; }
}
var company = new Company
{
Name = "ACME Corp",
Address = new Address { Street = "Main St 1", City = "Warsaw" },
Employees = new List<Employee>
{
new Employee { Name = "Jan", Position = "CEO" },
new Employee { Name = "Anna", Position = "CTO" }
}
};
string json = JsonSerializer.Serialize(company, new JsonSerializerOptions { WriteIndented = true });
// {
// "Name": "ACME Corp",
// "Address": {
// "Street": "Main St 1",
// "City": "Warsaw"
// },
// "Employees": [
// { "Name": "Jan", "Position": "CEO" },
// { "Name": "Anna", "Position": "CTO" }
// ]
// }
Polymorphic serialization
// Polymorphic serialization (.NET 7+)
[JsonDerivedType(typeof(Dog), typeDiscriminator: "dog")]
[JsonDerivedType(typeof(Cat), typeDiscriminator: "cat")]
public abstract class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
public string Breed { get; set; }
}
public class Cat : Animal
{
public int Lives { get; set; }
}
// Serialize z type discriminator
Animal animal = new Dog { Name = "Burek", Breed = "Labrador" };
string json = JsonSerializer.Serialize<Animal>(animal, new JsonSerializerOptions { WriteIndented = true });
// {
// "$type": "dog", ← type discriminator!
// "Name": "Burek",
// "Breed": "Labrador"
// }
// Deserialize - automatycznie tworzy Dog!
Animal deserialized = JsonSerializer.Deserialize<Animal>(json);
Console.WriteLine(deserialized is Dog); // True ✨
Custom Converters
JsonConverter - własna logika serialization
// Custom converter dla DateTime → Unix timestamp
public class UnixTimestampConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
long timestamp = reader.GetInt64();
return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
long timestamp = new DateTimeOffset(value).ToUnixTimeSeconds();
writer.WriteNumberValue(timestamp);
}
}
// Użycie - attribute na property
public class Event
{
public string Name { get; set; }
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime Timestamp { get; set; }
}
var evt = new Event
{
Name = "Meeting",
Timestamp = new DateTime(2026, 3, 12, 10, 30, 0)
};
string json = JsonSerializer.Serialize(evt);
// {"Name":"Meeting","Timestamp":1773151800} ← Unix timestamp! ✨
// Deserialize - konwertuje timestamp z powrotem do DateTime
Event deserialized = JsonSerializer.Deserialize<Event>(json);
Console.WriteLine(deserialized.Timestamp); // 2026-03-12 10:30:00
Praktyczne przykłady converters
// Converter 1: String → Enum (case-insensitive)
public class StringEnumConverter : JsonConverter<Status>
{
public override Status Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string value = reader.GetString();
return Enum.Parse<Status>(value, ignoreCase: true);
}
public override void Write(Utf8JsonWriter writer, Status value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString().ToLowerInvariant());
}
}
// Converter 2: Money type (amount + currency)
public class MoneyConverter : JsonConverter<Money>
{
public override Money Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// JSON: "100.50 PLN"
string value = reader.GetString();
var parts = value.Split(' ');
return new Money(decimal.Parse(parts[0]), parts[1]);
}
public override void Write(Utf8JsonWriter writer, Money value, JsonSerializerOptions options)
{
writer.WriteStringValue($"{value.Amount} {value.Currency}");
}
}
// Użycie
public class Order
{
[JsonConverter(typeof(MoneyConverter))]
public Money Total { get; set; }
}
var order = new Order { Total = new Money(100.50m, "PLN") };
string json = JsonSerializer.Serialize(order);
// {"Total":"100.50 PLN"}
Rejestracja converter globalnie
// Global converters - dla wszystkich serializacji
var options = new JsonSerializerOptions
{
Converters =
{
new UnixTimestampConverter(),
new MoneyConverter(),
new JsonStringEnumConverter() // Built-in enum converter
}
};
// Teraz wszystkie DateTime używają UnixTimestampConverter automatycznie!
string json = JsonSerializer.Serialize(anyObject, options);
🔥 Source Generators dla JSON (.NET 6+)
Problem z reflection
// Problem: Standardowa serialization używa Reflection
var person = new Person { Name = "Jan", Age = 30 };
string json = JsonSerializer.Serialize(person);
// W runtime:
// 1. Reflection - GetType(), GetProperties() (WOLNE!)
// 2. Dynamic code generation (alokacje)
// 3. Nie działa z AOT (Ahead-of-Time compilation)
Source Generators - compile-time serialization!
🎉 .NET 6 - JSON Source Generators
Generuj serialization code w COMPILE-TIME! Zero reflection, ultra-szybkie, AOT-compatible! ✨
// Source Generator - define context
[JsonSerializable(typeof(Person))]
[JsonSerializable(typeof(Company))]
[JsonSerializable(typeof(List<Person>))]
public partial class MyJsonContext : JsonSerializerContext
{
}
// Użycie - pass context
var person = new Person { Name = "Jan", Age = 30 };
// Source Generator wygenerował kod serialization w compile-time!
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person);
// Deserialize
Person deserialized = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);
// Zalety:
// ✅ Zero reflection - wszystko compile-time
// ✅ Szybsze - ~2x performance boost
// ✅ Mniejsze alokacje
// ✅ AOT compatible (Native AOT)
// ✅ Trimming-friendly
Source Generator z opcjami
// Context z custom options
[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata
)]
[JsonSerializable(typeof(Person))]
[JsonSerializable(typeof(List<Person>))]
public partial class MyJsonContext : JsonSerializerContext
{
}
// Wszystkie serialization używają tych options automatycznie!
var people = new List<Person>
{
new Person { Name = "Jan", Age = 30 },
new Person { Name = "Anna", Age = 25 }
};
string json = JsonSerializer.Serialize(people, MyJsonContext.Default.ListPerson);
// [
// {"name":"Jan","age":30}, ← camelCase z options!
// {"name":"Anna","age":25}
// ]
Kiedy używać Source Generators?
// ✅ Używaj Source Generators gdy:
// - Performance jest krytyczny
// - Chcesz AOT compilation
// - Tworzysz library
// - API endpoints (ASP.NET Core)
// - Znasz typy w compile-time
// ❌ NIE używaj gdy:
// - Dynamiczne typy (reflection required)
// - Prototyping (szybsze iteracje bez generatorów)
// - Bardzo złożone hierarchie typów
// ASP.NET Core - automatic context
// W .NET 8+ ASP.NET może auto-generować context!
app.MapGet("/api/person", () => new Person { Name = "Jan" });
// ASP.NET wygeneruje JsonContext automatycznie! ✨
System.Text.Json vs Newtonsoft.Json
Feature
System.Text.Json
Newtonsoft.Json
Performance
2-3x szybsze
Wolniejsze
Memory
Mniej alokacji
Więcej alokacji
Instalacja
Built-in .NET
NuGet package
Source Generators
✅ Tak (.NET 6+)
❌ Nie
AOT Compatible
✅ Tak
❌ Nie
Features
Standardowe + modern
Więcej legacy features
API Surface
Mniejszy, focused
Większy, wszystko ma
Domyślne
Stricter (bezpieczniejsze)
Permissive (łatwiejsze)
Migration: Newtonsoft → System.Text.Json
// Newtonsoft.Json (stary kod)
using Newtonsoft.Json;
string json = JsonConvert.SerializeObject(person);
Person p = JsonConvert.DeserializeObject<Person>(json);
[JsonProperty("full_name")]
public string Name { get; set; }
// System.Text.Json (nowy kod)
using System.Text.Json;
string json = JsonSerializer.Serialize(person);
Person p = JsonSerializer.Deserialize<Person>(json);
[JsonPropertyName("full_name")]
public string Name { get; set; }
// Główne różnice:
// 1. JsonConvert → JsonSerializer
// 2. JsonProperty → JsonPropertyName
// 3. JsonIgnore działa tak samo
// 4. Converters - inna składnia
// 5. Options - różne API
Kiedy zostać przy Newtonsoft?
// Zostań przy Newtonsoft.Json gdy:
// 1. Legacy codebase z dużą ilością Newtonsoft code
// 2. Potrzebujesz bardzo specyficznych features:
// - JObject/JToken dynamic manipulation
// - LINQ to JSON
// - Bardzo customowe converters
// - DateTimeZoneHandling specyficzne
// 3. Third-party libraries wymagają Newtonsoft
// Ale w nowych projektach (.NET 6+):
// ✅ Używaj System.Text.Json jako default
// ✅ Szybsze, bezpieczniejsze, moderne
// ✅ Source Generators dla ultra-performance
Praktyczne przykłady
API endpoint z JSON
// ASP.NET Core minimal API
app.MapPost("/api/users", async (CreateUserRequest request) =>
{
// ASP.NET automatycznie deserializuje JSON → object
var user = new User
{
Name = request.Name,
Email = request.Email
};
await _repository.SaveAsync(user);
// ASP.NET automatycznie serializuje object → JSON
return Results.Ok(new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email
});
});
// Request: POST /api/users
// Body: {"name":"Jan","email":"jan@example.com"}
// Response: {"id":1,"name":"Jan","email":"jan@example.com"}
// Z Source Generator dla performance
[JsonSerializable(typeof(CreateUserRequest))]
[JsonSerializable(typeof(UserResponse))]
public partial class ApiJsonContext : JsonSerializerContext { }
// Configure w Program.cs
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonContext.Default);
});
Configuration files
// Czytanie JSON config
public class AppSettings
{
public string ConnectionString { get; set; }
public LoggingSettings Logging { get; set; }
public List<string> AllowedHosts { get; set; }
}
public class LoggingSettings
{
public string Level { get; set; }
public string FilePath { get; set; }
}
// Load z pliku
string json = await File.ReadAllTextAsync("appsettings.json");
AppSettings settings = JsonSerializer.Deserialize<AppSettings>(json);
Console.WriteLine(settings.ConnectionString);
Console.WriteLine(settings.Logging.Level);
// Save do pliku
var newSettings = new AppSettings
{
ConnectionString = "Server=localhost;Database=MyDb",
Logging = new LoggingSettings { Level = "Information", FilePath = "logs.txt" },
AllowedHosts = new List<string> { "localhost", "example.com" }
};
string json2 = JsonSerializer.Serialize(newSettings, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync("appsettings.json", json2);
HTTP Client z JSON
// HttpClient extensions dla JSON (.NET 5+)
using var httpClient = new HttpClient();
// GET z deserialize
var user = await httpClient.GetFromJsonAsync<User>("https://api.example.com/users/1");
Console.WriteLine(user.Name);
// POST z serialize
var newUser = new CreateUserRequest { Name = "Jan", Email = "jan@example.com" };
var response = await httpClient.PostAsJsonAsync("https://api.example.com/users", newUser);
if (response.IsSuccessStatusCode)
{
var created = await response.Content.ReadFromJsonAsync<User>();
Console.WriteLine($"Created user ID: {created.Id}");
}
// PUT, DELETE też mają JSON extensions
await httpClient.PutAsJsonAsync($"https://api.example.com/users/{id}", updatedUser);
await httpClient.DeleteAsync($"https://api.example.com/users/{id}");