1. Introduction
If you're just starting to learn about serialization in .NET, it's natural to ask: why another library when there's already the built-in System.Text.Json? The answer is simple: Newtonsoft.Json showed up earlier and over the years evolved into still one of the most flexible JSON tools in .NET.
It became the de-facto standard because it supports complex scenarios: custom contracts, powerful converters, serialization of private fields, LINQ to JSON, dynamic objects (JObject), flexible handling of cyclic references and many date/time formats. Many libraries and APIs still use Json.NET "under the hood".
Important: in some scenarios the built-in System.Text.Json is still behind the capabilities of Newtonsoft.Json, so learning Json.NET remains relevant.
How to add Newtonsoft.Json (Json.NET)
Install the package via NuGet:
dotnet add package Newtonsoft.Json
Include the namespace:
using Newtonsoft.Json;
2. Serialization with Newtonsoft.Json
Let's take the usual class Person:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Serialize an object to a JSON string
Person person = new Person { Name = "Ivan", Age = 30 };
// Serialize to JSON
string json = JsonConvert.SerializeObject(person);
Console.WriteLine(json);
// Output: {"Name":"Ivan","Age":30}
Deserialize JSON back to an object
string json = "{\"Name\":\"Ivan\",\"Age\":30}";
Person person = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine($"{person.Name}, {person.Age}");
// Output: Ivan, 30
What happens "under the hood"?
Newtonsoft.Json goes through all public properties (public), serializes them to JSON and writes them to a string. On deserialization it maps keys from JSON to property names and fills the object.
3. Serialization of object collections
Serializing collections and arrays
Collections work "out of the box".
List<Person> people = new List<Person>
{
new Person { Name = "Ivan", Age = 30 },
new Person { Name = "Maria", Age = 25 }
};
string json = JsonConvert.SerializeObject(people);
// Output: [{"Name":"Ivan","Age":30},{"Name":"Maria","Age":25}]
List<Person> deserialized = JsonConvert.DeserializeObject<List<Person>>(json);
// Now you have a list of Person again!
Features of serializing and deserializing dictionaries
var dict = new Dictionary<string, int>
{
["apple"] = 2,
["banana"] = 5
};
string json = JsonConvert.SerializeObject(dict);
// Output: {"apple":2,"banana":5}
var deserializedDict = JsonConvert.DeserializeObject<Dictionary<string,int>>(json);
// Everything works!
If keys are not strings (for example, Dictionary<int,string>), Json.NET converts keys to strings when serializing, and on deserialization it will try to convert them back. For complex keys (for example, Guid) it's safer to use Dictionary<string, TValue>.
4. Working with nested objects and hierarchies
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 = "Ivan", Age = 30 },
Products = new List<Product>
{
new Product { Title = "Laptop", Price = 50000.0 },
new Product { Title = "Mouse", Price = 1500.0 }
}
};
string json = JsonConvert.SerializeObject(order);
Console.WriteLine(json);
Result: nested objects will be correctly represented inside the JSON structure.
5. Configuring serialization with attributes
Ignore a property
public class Person
{
public string Name { get; set; }
[JsonIgnore]
public int Age { get; set; }
}
Now Age will not appear in the JSON.
Rename a property
public class Person
{
[JsonProperty("full_name")]
public string Name { get; set; }
}
In JSON the name will be "full_name".
6. Flexible configuration: JsonSerializerSettings
Formatting (pretty multi-line JSON):
string json = JsonConvert.SerializeObject(
people,
Formatting.Indented
);
Result:
[
{
"Name": "Ivan",
"Age": 30
},
{
"Name": "Maria",
"Age": 25
}
]
Commonly used settings:
| Property | Description |
|---|---|
|
How to handle null properties (skip them or write explicit null) |
|
Whether to skip default values |
|
What to do with reference loops |
|
Format string for dates and times |
Example that skips null:
string json = JsonConvert.SerializeObject(
person,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }
);
7. Working with dynamic JSON structures: JObject, JArray and others
When the JSON structure is unknown upfront, use types from Newtonsoft.Json.Linq:
using Newtonsoft.Json.Linq;
string json = @"{
'Name': 'Ivan',
'Age': 30,
'Skills': ['C#', 'SQL', 'JSON']
}";
JObject obj = JObject.Parse(json);
Console.WriteLine(obj["Name"]); // Ivan
Console.WriteLine(obj["Skills"][0]); // C#
Creating JSON "on the fly":
var jObj = new JObject
{
["Status"] = "Success",
["Result"] = new JArray("item1", "item2", "item3")
};
Console.WriteLine(jObj.ToString(Formatting.Indented));
JObject and JArray are representations of a JSON object and array — basically convenient collections.
8. Useful nuances
Cyclic references
Newtonsoft.Json can handle them flexibly:
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);
This way loops will be skipped. To preserve references you can use ReferenceLoopHandling.Serialize together with [JsonObject(IsReference = true)].
Serializing anonymous and dynamic objects
var anon = new { Foo = 42, Bar = "Hello" };
string json = JsonConvert.SerializeObject(anon);
// {"Foo":42,"Bar":"Hello"}
Validation and error handling
try
{
Person p = JsonConvert.DeserializeObject<Person>(brokenJson);
}
catch (JsonSerializationException ex)
{
Console.WriteLine("Error during deserialization: " + ex.Message);
}
Comparison: Newtonsoft.Json vs System.Text.Json
| Newtonsoft.Json (Json.NET) | System.Text.Json (.NET) | |
|---|---|---|
| Supported .NET | .NET Framework/Standard/6+ | .NET Core 3.0+ / .NET 5/6/9 |
| LINQ to JSON (JObject/JArray) | Yes | No |
| Flexible configuration | Very large | Limited |
| Attributes ([JsonProperty], ...) | Yes | Yes (partially, fewer capabilities) |
| Support for private properties | Yes | No |
| Speed | Slower in some scenarios | Faster |
| Complex converters | Yes | Yes (less flexible for now) |
| Support for DataTable, DataSet | Yes | No |
| Documentation and examples | Lots | Growing |
9. Beginner mistakes and common pitfalls
Mistake #1: properties become null after deserialization.
Often a property has no setter or there's no parameterless constructor — the serializer has nothing to populate the object with.
Mistake #2: property names mismatch between JSON and the class.
If JSON field is "fullName" but the class has FullName, use [JsonProperty] or configure a ContractResolver to map names.
Mistake #3: serialization only works with public properties by default.
Private fields/properties are not serialized without extra settings. You need converters or special resolvers/contracts.
Mistake #4: cyclic references lead to StackOverflowException.
Mutual object references can loop serialization without settings. Configure reference handling (for example, ReferenceLoopHandling) or change the data model.
GO TO FULL VERSION