Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

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!

📅 Timeline - JSON w .NET
  • 2010 - Newtonsoft.Json dominuje rynek
  • .NET Core 3.0 (2019) - 🔥 System.Text.Json wprowadzony!
  • .NET 5 (2020) - Improvements, więcej features
  • .NET 6 (2021) - 🔥 Source Generators dla JSON!
  • .NET 7+ (2022+) - Required members, polymorphic serialization
🔍 Dlaczego System.Text.Json?

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

JsonSerializerOptions - konfiguracja

// Options - dostosuj serialization behavior
var options = new JsonSerializerOptions
{
    // Pretty print (formatowanie)
    WriteIndented = true,
    
    // Property naming policy (camelCase)
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    
    // Ignore null values
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    
    // Allow trailing commas
    AllowTrailingCommas = true,
    
    // Case insensitive property names
    PropertyNameCaseInsensitive = true,
    
    // Number handling
    NumberHandling = JsonNumberHandling.AllowReadingFromString
};

// Użycie
string json = JsonSerializer.Serialize(person, options);

// Default options dla całej aplikacji
JsonSerializerOptions.Default.WriteIndented = true;

Attributes - kontrola serialization

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}");
Podsumowanie

  • System.Text.Json - modern, szybki, built-in
  • Serialize/Deserialize - podstawy, options, attributes
  • Custom converters - własna logika serialization
  • 🔥 Source Generators - compile-time, zero reflection, ultra-szybkie!
  • vs Newtonsoft - 2-3x szybsze, AOT-compatible
  • Praktyczne użycie - API, config files, HTTP client

Następny wpis: Dependency Injection i IoC!