1. Wprowadzenie
Jeśli dopiero zaczynasz zapoznawać się z serializacją w .NET, całkiem naturalne pytanie brzmi: po co kolejna biblioteka, skoro jest już wbudowany System.Text.Json? Odpowiedź prosta: Newtonsoft.Json pojawił się wcześniej i przez lata rozwinął się w nadal najbardziej elastyczne narzędzie do JSON w .NET.
Stał się de-facto standardem, bo obsługuje skomplikowane scenariusze: customowe kontrakty, potężne konwertery, serializację prywatnych pól, LINQ do JSON, dynamiczne obiekty (JObject), elastyczną obsługę cyklicznych odniesień i mnóstwo formatów dat/czasu. Wiele bibliotek i API wciąż używa Json.NET "pod maską".
Ważne: w pewnych scenariuszach wbudowany System.Text.Json wciąż ustępuje możliwościom Newtonsoft.Json, więc nauka Json.NET jest nadal przydatna.
Jak dodać Newtonsoft.Json (Json.NET)
Zainstaluj pakiet przez NuGet:
dotnet add package Newtonsoft.Json
Dodaj przestrzeń nazw:
using Newtonsoft.Json;
2. Serializacja z użyciem Newtonsoft.Json
Weźmy tradycyjną klasę Person:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Serializacja obiektu do stringa JSON
Person person = new Person { Name = "Iwan", Age = 30 };
// Serializacja do JSON
string json = JsonConvert.SerializeObject(person);
Console.WriteLine(json);
// Wynik: {"Name":"Iwan","Age":30}
Deserializacja JSON z powrotem do obiektu
string json = "{\"Name\":\"Iwan\",\"Age\":30}";
Person person = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine($"{person.Name}, {person.Age}");
// Wynik: Iwan, 30
Co się dzieje "pod maską"?
Newtonsoft.Json iteruje po wszystkich publicznych właściwościach (public), serializuje je do JSON i zapisuje w stringu. Przy deserializacji dopasowuje klucze z JSON do nazw właściwości i wypełnia obiekt.
3. Serializacja kolekcji obiektów
Serializacja kolekcji i tablic
Z kolekcjami działa to "od ręki".
List<Person> people = new List<Person>
{
new Person { Name = "Iwan", Age = 30 },
new Person { Name = "Maria", Age = 25 }
};
string json = JsonConvert.SerializeObject(people);
// Wynik: [{"Name":"Iwan","Age":30},{"Name":"Maria","Age":25}]
List<Person> deserialized = JsonConvert.DeserializeObject<List<Person>>(json);
// Masz z powrotem listę Person!
Szczegóły przy serializacji i deserializacji słowników
var dict = new Dictionary<string, int>
{
["apple"] = 2,
["banana"] = 5
};
string json = JsonConvert.SerializeObject(dict);
// Wynik: {"apple":2,"banana":5}
var deserializedDict = JsonConvert.DeserializeObject<Dictionary<string,int>>(json);
// Wszystko działa!
Jeśli klucze nie są stringami (np. Dictionary<int,string>), Json.NET zamieni klucze na stringi przy serializacji, a przy deserializacji spróbuje skonwertować z powrotem. Dla skomplikowanych kluczy (np. Guid) pewniej użyć Dictionary<string, TValue>.
4. Praca z obiektami zagnieżdżonymi i hierarchiami
public class Order
{
public int Id { get; set; }
public Person Customer { get; set; }
public List<Product> Products { get; set; }
}
public class Product
{
public string Title { get; set; }
public double Price { get; set; }
}
Order order = new Order
{
Id = 123,
Customer = new Person { Name = "Iwan", Age = 30 },
Products = new List<Product>
{
new Product { Title = "Laptop", Price = 50000.0 },
new Product { Title = "Mysz", Price = 1500.0 }
}
};
string json = JsonConvert.SerializeObject(order);
Console.WriteLine(json);
Rezultat: zagnieżdżone obiekty będą poprawnie przedstawione wewnątrz struktury JSON.
5. Konfigurowanie serializacji za pomocą atrybutów
Ignorowanie właściwości
public class Person
{
public string Name { get; set; }
[JsonIgnore]
public int Age { get; set; }
}
Teraz Age nie trafi do JSON.
Zmiana nazwy właściwości
public class Person
{
[JsonProperty("full_name")]
public string Name { get; set; }
}
W JSON nazwa będzie "full_name".
6. Elastyczna konfiguracja: JsonSerializerSettings
Formatowanie (ładny, wieloliniowy JSON):
string json = JsonConvert.SerializeObject(
people,
Formatting.Indented
);
Rezultat:
[
{
"Name": "Iwan",
"Age": 30
},
{
"Name": "Maria",
"Age": 25
}
]
Często używane ustawienia:
| Właściwość | Opis |
|---|---|
|
Jak obsługiwać właściwości null (pominąć je czy zapisać explicite null) |
|
Czy pomijać wartości domyślne |
|
Co robić z cyklicznymi referencjami |
|
Format string dla dat i czasu |
Przykład pomijania null:
string json = JsonConvert.SerializeObject(
person,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }
);
7. Praca z dynamicznymi strukturami JSON: JObject, JArray i inne
Gdy struktura JSON nie jest znana z góry, użyj typów z Newtonsoft.Json.Linq:
using Newtonsoft.Json.Linq;
string json = @"{
'Name': 'Iwan',
'Age': 30,
'Skills': ['C#', 'SQL', 'JSON']
}";
JObject obj = JObject.Parse(json);
Console.WriteLine(obj["Name"]); // Iwan
Console.WriteLine(obj["Skills"][0]); // C#
Tworzenie JSON "w locie":
var jObj = new JObject
{
["Status"] = "Success",
["Result"] = new JArray("item1", "item2", "item3")
};
Console.WriteLine(jObj.ToString(Formatting.Indented));
JObject i JArray — reprezentacje obiektu JSON i tablicy, w praktyce wygodne kolekcje.
8. Przydatne niuanse
Cykliczne referencje
Newtonsoft.Json potrafi je elastycznie obsługiwać:
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);
W ten sposób cykle zostaną pominięte. Aby zachować referencje można użyć ReferenceLoopHandling.Serialize razem z [JsonObject(IsReference = true)].
Serializacja anonimowych i dynamicznych obiektów
var anon = new { Foo = 42, Bar = "Hello" };
string json = JsonConvert.SerializeObject(anon);
// {"Foo":42,"Bar":"Hello"}
Walidacja i obsługa błędów
try
{
Person p = JsonConvert.DeserializeObject<Person>(brokenJson);
}
catch (JsonSerializationException ex)
{
Console.WriteLine("Błąd podczas deserializacji: " + ex.Message);
}
Porównanie Newtonsoft.Json vs System.Text.Json
| Newtonsoft.Json (Json.NET) | System.Text.Json (.NET) | |
|---|---|---|
| Wsparcie .NET | .NET Framework/Standard/6+ | .NET Core 3.0+ / .NET 5/6/9 |
| LINQ do JSON (JObject/JArray) | Tak | Nie |
| Elastyczna konfiguracja | Bardzo szeroka | Ograniczona |
| Atrybuty ([JsonProperty], ...) | Tak | Tak (częściowo, mniej możliwości) |
| Wsparcie prywatnych właściwości | Tak | Nie |
| Szybkość | Wolniejsze w niektórych scenariuszach | Szybsze |
| Zaawansowane konwertery | Tak | Tak (mniej elastyczne, póki co) |
| Wsparcie DataTable, DataSet | Tak | Nie |
| Dokumentacja i przykłady | Mnóstwo | Rośnie |
9. Błędy początkujących i typowe pułapki
Błąd #1: właściwości stają się null po deserializacji.
Często właściwość nie ma settera albo brakuje konstruktora bezparametrowego — serializator nie ma jak wypełnić obiektu.
Błąd #2: niezgodność nazw właściwości między JSON a klasą.
Jeśli w JSON pole ma nazwę "fullName", a w klasie — FullName, użyj [JsonProperty] lub skonfiguruj ContractResolver do mapowania nazw.
Błąd #3: serializacja działa domyślnie tylko z publicznymi właściwościami.
Prywatne pola/właściwości nie są serializowane bez dodatkowych ustawień. Potrzebne są konwertery lub specjalne resolvery/kontrakty.
Błąd #4: cykliczne referencje prowadzą do StackOverflowException.
Wzajemne referencje obiektów mogą zapętlić serializację bez właściwej konfiguracji. Konfiguruj obsługę referencji (np. ReferenceLoopHandling) lub zmień model danych.
GO TO FULL VERSION