Newtonsoft.Json in C#: Serialize, Deserialize, JObject & Custom Converters

Last updated:

Newtonsoft.Json (Json.NET) serializes C# objects to JSON with JsonConvert.SerializeObject(obj) and deserializes JSON strings back to typed objects with JsonConvert.DeserializeObject<T>(json) — 2.4 billion NuGet downloads make it the most-used .NET library ever. For dynamic or unknown JSON structure, JObject.Parse(json) returns a navigable tree where jObj["key"] accesses values and jObj.SelectToken("$.users[0].email") applies JSONPath queries. JsonSerializerSettings centralizes serialization options: NullValueHandling.Ignore omits null properties, CamelCasePropertyNamesContractResolver converts PascalCase C# to camelCase JSON automatically. This guide covers JsonConvert serialization, JObject/JArray/JToken for dynamic JSON, [JsonProperty]/[JsonIgnore] attributes, JsonSerializerSettings, writing custom JsonConverter classes, and the migration path from Newtonsoft.Json to System.Text.Json.

Serializing and Deserializing with JsonConvert

JsonConvert is the primary entry point for Newtonsoft.Json serialization. SerializeObject converts any C# object to a JSON string; DeserializeObject<T> parses a JSON string into a typed C# object. Both methods accept an optional JsonSerializerSettings parameter. For collections, deserialize into List<T> or T[]. For compact output, pass Formatting.None; for indented output, pass Formatting.Indented.

using Newtonsoft.Json;
using System.Collections.Generic;

// ── Define a C# class to map to JSON ──────────────────────────────
public class User
{
    public int    Id       { get; set; }
    public string Name     { get; set; }
    public string Email    { get; set; }
    public bool   IsActive { get; set; }
}

// ── Serialize: C# object → JSON string ────────────────────────────
var user = new User { Id = 1, Name = "Alice", Email = "alice@example.com", IsActive = true };

string json = JsonConvert.SerializeObject(user);
// {"Id":1,"Name":"Alice","Email":"alice@example.com","IsActive":true}

string prettyJson = JsonConvert.SerializeObject(user, Formatting.Indented);
// {
//   "Id": 1,
//   "Name": "Alice",
//   "Email": "alice@example.com",
//   "IsActive": true
// }

// ── Deserialize: JSON string → typed C# object ────────────────────
string input = @"{""Id"":2,""Name"":""Bob"",""Email"":""bob@example.com"",""IsActive"":false}";
User deserialized = JsonConvert.DeserializeObject<User>(input);
Console.WriteLine(deserialized.Name);  // Bob
Console.WriteLine(deserialized.Id);    // 2

// ── Serialize and deserialize collections ─────────────────────────
var users = new List<User>
{
    new User { Id = 1, Name = "Alice", Email = "alice@example.com", IsActive = true },
    new User { Id = 2, Name = "Bob",   Email = "bob@example.com",   IsActive = false },
};

string usersJson = JsonConvert.SerializeObject(users, Formatting.Indented);
// [{ "Id": 1, ... }, { "Id": 2, ... }]

List<User> parsedUsers = JsonConvert.DeserializeObject<List<User>>(usersJson);
Console.WriteLine(parsedUsers.Count);  // 2

// ── Serialize anonymous objects (useful for quick payloads) ────────
string anonymousJson = JsonConvert.SerializeObject(new
{
    status  = "success",
    message = "User created",
    userId  = 42
});
// {"status":"success","message":"User created","userId":42}

// ── Deserialize to Dictionary when schema is unknown ──────────────
string dynamicJson = @"{""foo"":1,""bar"":""hello"",""baz"":true}";
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(dynamicJson);
Console.WriteLine(dict["bar"]);  // hello

DeserializeObject<T> returns null when the input JSON is the string "null" or an empty string — always null-check the result when deserializing untrusted input. For async I/O pipelines, use JsonSerializer.DeserializeAsync<T>(stream) from the underlying JsonSerializer class to avoid loading the entire JSON string into memory before parsing.

Dynamic JSON with JObject, JArray, and JToken

The LINQ to JSON API — JObject, JArray, JToken, and JValue — lets you parse and navigate JSON without a predefined C# class. JObject.Parse(json) creates a dictionary-like object; JArray.Parse(json) creates a list-like collection. Access values with string indexers (jObj["key"]), cast with .Value<T>(), and traverse nested structures by chaining indexers.

using Newtonsoft.Json.Linq;

string json = @"
{
  ""id"": 101,
  ""name"": ""Alice"",
  ""address"": {
    ""city"": ""Seattle"",
    ""zip"":  ""98101""
  },
  ""scores"": [95, 87, 92]
}";

// ── Parse to JObject ───────────────────────────────────────────────
JObject jObj = JObject.Parse(json);

// Access top-level fields
string name = jObj["name"].Value<string>();   // Alice
int    id   = jObj["id"].Value<int>();        // 101

// Access nested fields
string city = jObj["address"]["city"].Value<string>();  // Seattle

// Access array elements
JArray scores   = (JArray)jObj["scores"];
int    firstScore = scores[0].Value<int>();  // 95

// ── JObject iteration ─────────────────────────────────────────────
foreach (var prop in jObj.Properties())
{
    Console.WriteLine($"{prop.Name}: {prop.Value}");
}

// ── Build JObject programmatically ────────────────────────────────
var newObj = new JObject
{
    ["status"]  = "ok",
    ["count"]   = 3,
    ["items"]   = new JArray("a", "b", "c"),
};
string output = newObj.ToString(Formatting.Indented);

// ── JArray.Parse for JSON arrays ──────────────────────────────────
string arrayJson = @"[{""id"":1,""name"":""Alice""},{""id"":2,""name"":""Bob""}]";
JArray jArr = JArray.Parse(arrayJson);

foreach (JToken token in jArr)
{
    string userName = token["name"].Value<string>();
    Console.WriteLine(userName);  // Alice, Bob
}

// ── JToken — the base type for all LINQ to JSON nodes ─────────────
// JObject, JArray, JValue all derive from JToken
JToken root = JToken.Parse(json);
if (root.Type == JTokenType.Object)
{
    JObject obj = (JObject)root;
    // navigate as JObject
}

// ── Convert JObject back to a typed class ─────────────────────────
User user = jObj.ToObject<User>();  // maps matching property names
Console.WriteLine(user.Name);       // Alice

JToken.Type returns a JTokenType enum value (Object, Array, String, Integer, Boolean, Null, etc.) for safe type-checking before casting. Use jObj["key"]?.Value<string>() with null-conditional access to avoid NullReferenceException when a key may not exist in the JSON.

Controlling Output with JsonProperty and JsonIgnore Attributes

Newtonsoft.Json attributes on C# properties fine-tune serialization behavior without custom converters or global settings. [JsonProperty("name")] maps a C# property to a different JSON key name. [JsonIgnore] excludes a property from both serialization and deserialization. [JsonProperty(Required = Required.Always)] throws a JsonSerializationException if the key is missing during deserialization.

using Newtonsoft.Json;
using System;

public class Order
{
    // ── [JsonProperty] — rename in JSON output ────────────────────
    [JsonProperty("order_id")]
    public int OrderId { get; set; }

    [JsonProperty("customer_name")]
    public string CustomerName { get; set; }

    // ── [JsonProperty] with Required — enforce presence ───────────
    [JsonProperty("total_amount", Required = Required.Always)]
    public decimal TotalAmount { get; set; }

    // ── [JsonIgnore] — exclude from serialization AND deserialization
    [JsonIgnore]
    public string InternalNotes { get; set; }

    // ── [JsonProperty] with NullValueHandling — per-property null control
    [JsonProperty("discount", NullValueHandling = NullValueHandling.Ignore)]
    public decimal? Discount { get; set; }

    // ── [JsonProperty] with DefaultValueHandling — omit if default ─
    [JsonProperty("is_shipped", DefaultValueHandling = DefaultValueHandling.Ignore)]
    public bool IsShipped { get; set; }

    // ── [JsonConverter] — per-property custom converter ───────────
    [JsonConverter(typeof(UnixDateTimeConverter))]
    public DateTime CreatedAt { get; set; }
}

// ── Serialization result ──────────────────────────────────────────
var order = new Order
{
    OrderId       = 1001,
    CustomerName  = "Alice",
    TotalAmount   = 149.99m,
    InternalNotes = "Internal: flagged for review",  // excluded
    Discount      = null,                             // omitted (NullValueHandling.Ignore)
    IsShipped     = false,                            // omitted (DefaultValueHandling.Ignore)
    CreatedAt     = new DateTime(2026, 5, 28),
};

string json = JsonConvert.SerializeObject(order, Formatting.Indented);
// {
//   "order_id": 1001,
//   "customer_name": "Alice",
//   "total_amount": 149.99,
//   "created_at": 1748390400   (Unix timestamp from UnixDateTimeConverter)
// }

// ── [JsonExtensionData] — capture unknown JSON fields ─────────────
using System.Collections.Generic;
using Newtonsoft.Json.Linq;

public class FlexibleUser
{
    public string Name { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JToken> ExtraFields { get; set; }
}

string input = @"{""Name"":""Bob"",""role"":""admin"",""region"":""EU""}";
var flexUser = JsonConvert.DeserializeObject<FlexibleUser>(input);
Console.WriteLine(flexUser.Name);                        // Bob
Console.WriteLine(flexUser.ExtraFields["role"]);         // admin
Console.WriteLine(flexUser.ExtraFields["region"]);       // EU

[JsonExtensionData] on a Dictionary<string, JToken> property captures all JSON fields that do not map to any other C# property — useful for forward-compatible deserialization when new fields may be added to an API response without breaking existing code. The dictionary is also serialized back out, so round-tripping preserves unknown fields.

JsonSerializerSettings: Null Handling, Formatting, and ContractResolver

JsonSerializerSettings is the central configuration object for Newtonsoft.Json. Pass it as the second argument to SerializeObject and DeserializeObject, or register a global default via JsonConvert.DefaultSettings. The most commonly used settings are NullValueHandling, ContractResolver, DateFormatString, ReferenceLoopHandling, and Converters.

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;

// ── NullValueHandling — omit null properties ──────────────────────
var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
};
string json = JsonConvert.SerializeObject(
    new { name = "Alice", email = (string)null },
    settings
);
// {"name":"Alice"}   — email omitted because it is null

// ── ContractResolver — CamelCase output ───────────────────────────
var camelSettings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
var user = new { FirstName = "Alice", LastName = "Smith", UserId = 42 };
string camelJson = JsonConvert.SerializeObject(user, camelSettings);
// {"firstName":"Alice","lastName":"Smith","userId":42}

// ── ContractResolver — SnakeCase output ──────────────────────────
var snakeSettings = new JsonSerializerSettings
{
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy(),
    },
};
string snakeJson = JsonConvert.SerializeObject(user, snakeSettings);
// {"first_name":"Alice","last_name":"Smith","user_id":42}

// ── DateFormatString — custom date serialization ──────────────────
var dateSettings = new JsonSerializerSettings
{
    DateFormatString = "yyyy-MM-dd",
};
var evt = new { Name = "Launch", Date = new System.DateTime(2026, 5, 28) };
string dateJson = JsonConvert.SerializeObject(evt, dateSettings);
// {"Name":"Launch","Date":"2026-05-28"}

// ── ReferenceLoopHandling — handle circular references ────────────
public class Node
{
    public string Value  { get; set; }
    public Node   Parent { get; set; }  // circular reference
}
var child = new Node { Value = "child" };
var parent = new Node { Value = "parent", };
child.Parent = parent;  // circular!

string safeJson = JsonConvert.SerializeObject(child, new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    Formatting            = Formatting.Indented,
});
// Parent property is omitted on the second visit to break the cycle

// ── JsonConvert.DefaultSettings — set globally for the application ─
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
    NullValueHandling    = NullValueHandling.Ignore,
    ContractResolver     = new CamelCasePropertyNamesContractResolver(),
    Formatting           = Formatting.None,
};
// Now all JsonConvert.SerializeObject/DeserializeObject calls use these settings
// unless overridden per-call.

TypeNameHandling is a setting to be aware of for security: TypeNameHandling.Auto or TypeNameHandling.All embeds $type fields in the JSON output that Newtonsoft.Json uses to deserialize to the original concrete type. Never deserialize JSON from untrusted sources with TypeNameHandling set to anything other than None (the default) — it is a known deserialization gadget chain attack vector.

Writing Custom JsonConverter Classes

Custom converters let you fully control how a specific C# type is serialized and deserialized. Extend JsonConverter<T> and override WriteJson (serialization) and ReadJson (deserialization). Register globally via JsonSerializerSettings.Converters or per-property via [JsonConverter(typeof(MyConverter))]. Common use cases include custom date formats, enums as strings, polymorphic type discrimination, and serializing types from external libraries.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Drawing;

// ── Example 1: Serialize Color as "#RRGGBB" hex string ────────────
public class HexColorConverter : JsonConverter<Color>
{
    public override void WriteJson(JsonWriter writer, Color value, JsonSerializer serializer)
    {
        // Serialize Color as "#RRGGBB"
        writer.WriteValue($"#{value.R:X2}{value.G:X2}{value.B:X2}");
    }

    public override Color ReadJson(
        JsonReader reader, Type objectType, Color existingValue,
        bool hasExistingValue, JsonSerializer serializer)
    {
        // Deserialize "#RRGGBB" back to Color
        string hex   = reader.Value as string;
        int    argb  = Convert.ToInt32(hex.TrimStart('#'), 16);
        return Color.FromArgb(255, (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
    }
}

// ── Example 2: Polymorphic deserialization with type discriminator ─
public abstract class Shape { public string Type { get; set; } }
public class Circle    : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width  { get; set; } public double Height { get; set; } }

public class ShapeConverter : JsonConverter<Shape>
{
    public override Shape ReadJson(
        JsonReader reader, Type objectType, Shape existingValue,
        bool hasExistingValue, JsonSerializer serializer)
    {
        JObject jObj = JObject.Load(reader);
        string  type = jObj["type"]?.Value<string>();

        return type switch
        {
            "circle"    => jObj.ToObject<Circle>(serializer),
            "rectangle" => jObj.ToObject<Rectangle>(serializer),
            _           => throw new JsonSerializationException($"Unknown shape type: {type}"),
        };
    }

    public override void WriteJson(JsonWriter writer, Shape value, JsonSerializer serializer)
    {
        // Use default serialization — add type discriminator
        JObject jObj = JObject.FromObject(value, serializer);
        jObj["type"] = value.GetType().Name.ToLower();
        jObj.WriteTo(writer);
    }
}

// ── Register and use the converters ───────────────────────────────
var settings = new JsonSerializerSettings();
settings.Converters.Add(new ShapeConverter());

string circleJson    = @"{""type"":""circle"",""Radius"":5.0}";
string rectangleJson = @"{""type"":""rectangle"",""Width"":10.0,""Height"":4.0}";

Shape circle    = JsonConvert.DeserializeObject<Shape>(circleJson, settings);
Shape rectangle = JsonConvert.DeserializeObject<Shape>(rectangleJson, settings);

Console.WriteLine(circle    is Circle);    // True
Console.WriteLine(rectangle is Rectangle); // True
Console.WriteLine(((Circle)circle).Radius); // 5

Override CanRead or CanWrite (return false) to use the converter for only one direction — for example, a write-only converter that formats output but lets Newtonsoft.Json handle reading using default deserialization. This avoids re-implementing read logic when you only need to customize the output shape.

JSONPath Queries with SelectToken and SelectTokens

Newtonsoft.Json implements JSONPath (a query language for JSON similar to XPath for XML) via JToken.SelectToken(path) and JToken.SelectTokens(path). These methods work on any JToken instance — JObject, JArray, or nested tokens. JSONPath expressions start with $ (root), use . for property access, [] for array index or filter, and .. for recursive descent.

using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;

string json = @"
{
  ""store"": {
    ""name"": ""Tech Gadgets"",
    ""products"": [
      { ""id"": 1, ""name"": ""Laptop"",     ""price"": 999.00, ""inStock"": true  },
      { ""id"": 2, ""name"": ""Mouse"",      ""price"":  29.99, ""inStock"": true  },
      { ""id"": 3, ""name"": ""Monitor"",    ""price"": 349.00, ""inStock"": false },
      { ""id"": 4, ""name"": ""Headphones"", ""price"":  79.99, ""inStock"": true  }
    ]
  }
}";

JObject root = JObject.Parse(json);

// ── SelectToken — first match or null ─────────────────────────────
string storeName = root.SelectToken("$.store.name").Value<string>();
// "Tech Gadgets"

double laptopPrice = root.SelectToken("$.store.products[0].price").Value<double>();
// 999.00

// ── SelectTokens — all matches ────────────────────────────────────
// Get all product names
IEnumerable<JToken> names = root.SelectTokens("$.store.products[*].name");
// ["Laptop", "Mouse", "Monitor", "Headphones"]

// ── Filter expression: [?(@.field operator value)] ─────────────────
// Products in stock
IEnumerable<JToken> inStock = root.SelectTokens("$.store.products[?(@.inStock == true)]");
foreach (JToken product in inStock)
{
    Console.WriteLine($"{product["name"]}: ${product["price"]}");
}
// Laptop: $999.00
// Mouse: $29.99
// Headphones: $79.99

// ── Filter by price ───────────────────────────────────────────────
IEnumerable<JToken> affordable = root.SelectTokens("$.store.products[?(@.price < 100)]");
// Mouse ($29.99) and Headphones ($79.99)

// ── Recursive descent (..) — find all "price" fields at any depth ─
IEnumerable<JToken> allPrices = root.SelectTokens("$..price");
List<double> prices = allPrices.Select(t => t.Value<double>()).ToList();
// [999.00, 29.99, 349.00, 79.99]

// ── SelectToken returning null on no match (safe navigation) ───────
JToken missing = root.SelectToken("$.store.manager");
if (missing != null)
{
    Console.WriteLine(missing.Value<string>());
}
// Nothing printed — key does not exist

// ── SelectToken with Value<T>() shorthand ──────────────────────────
string firstProductName = root.SelectToken("$.store.products[0].name")?.Value<string>();
// "Laptop"

JSONPath filter expressions in Newtonsoft.Json support ==, !=, <, >, <=, >= operators and string matching. For complex filtering, combine SelectTokens with LINQ .Where() for more expressive queries — LINQ operates on the IEnumerable<JToken> returned by SelectTokens, allowing arbitrary C# predicates.

Migrating from Newtonsoft.Json to System.Text.Json

System.Text.Json (the built-in .NET JSON library since .NET 3.0) is 20–40% faster and has lower memory usage than Newtonsoft.Json. For .NET 6+ projects, it is the recommended choice for new development. Migration requires mapping Newtonsoft.Json concepts to System.Text.Json equivalents and addressing feature gaps — particularly around polymorphism, circular references, and LINQ to JSON.

// ── API equivalents: Newtonsoft.Json → System.Text.Json ──────────
//
// JsonConvert.SerializeObject(obj)
//   → JsonSerializer.Serialize(obj)
//
// JsonConvert.DeserializeObject<T>(json)
//   → JsonSerializer.Deserialize<T>(json)
//
// JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }
//   → new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }
//
// ContractResolver = new CamelCasePropertyNamesContractResolver()
//   → new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
//
// [JsonProperty("name")]          → [JsonPropertyName("name")]
// [JsonIgnore]                    → [JsonIgnore]
// JsonConverter<T>                → JsonConverter<T>  (same pattern, different namespace)
// JObject / JArray / JToken       → JsonNode / JsonArray / JsonElement  (System.Text.Json.Nodes)

using System.Text.Json;
using System.Text.Json.Serialization;

// ── Serialization with System.Text.Json ───────────────────────────
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy           = JsonNamingPolicy.CamelCase,
    DefaultIgnoreCondition         = JsonIgnoreCondition.WhenWritingNull,
    WriteIndented                  = true,
    ReferenceHandler               = ReferenceHandler.IgnoreCycles, // replaces ReferenceLoopHandling.Ignore
};

string json = JsonSerializer.Serialize(user, options);
User result = JsonSerializer.Deserialize<User>(json, options);

// ── Attribute migration ────────────────────────────────────────────
// Newtonsoft:    [JsonProperty("first_name")]
// System.Text.Json: [JsonPropertyName("first_name")]

// ── JsonNode — dynamic JSON (replaces JObject/JArray) ─────────────
using System.Text.Json.Nodes;

JsonNode root      = JsonNode.Parse(json);
string  name       = root["name"]?.GetValue<string>();
int     id         = root["id"]?.GetValue<int>() ?? 0;
JsonArray products = root["store"]?["products"]?.AsArray();

// ── Known gaps: features in Newtonsoft.Json not in System.Text.Json ─
//
// 1. TypeNameHandling  — no equivalent (by design, for security)
// 2. JObject LINQ queries (SelectToken/SelectTokens) — not available;
//    use JsonNode traversal or JsonDocument with JsonElement.
// 3. PreserveReferencesHandling with $id/$ref — partially supported
//    via ReferenceHandler.Preserve in .NET 6+
// 4. Custom NamingStrategies (snake_case) — use JsonNamingPolicy.SnakeCaseLower
//    in .NET 8+ or implement a custom JsonNamingPolicy subclass.
// 5. [JsonConstructor] for non-default constructors — supported in .NET 6+

// ── Migration tip: keep Newtonsoft.Json for complex scenarios ─────
// In ASP.NET Core, install Microsoft.AspNetCore.Mvc.NewtonsoftJson
// and call services.AddControllers().AddNewtonsoftJson() to keep
// Newtonsoft.Json for MVC while using System.Text.Json elsewhere.

The migration decision is often pragmatic: projects with extensive custom converters, TypeNameHandling polymorphism, or heavy LINQ to JSON usage are better served staying on Newtonsoft.Json. New projects or services where performance is critical (high-throughput APIs, AOT compilation for AWS Lambda) benefit most from switching to System.Text.Json. Use the official Microsoft migration guide for a complete feature comparison table.

FAQ

How do I serialize and deserialize JSON with Newtonsoft.Json?

Install the Newtonsoft.Json NuGet package (over 2.4 billion total downloads), then call JsonConvert.SerializeObject(obj) to convert any C# object to a JSON string and JsonConvert.DeserializeObject<T>(json) to convert a JSON string back to a typed C# object. Define a C# class matching the JSON structure, decorate properties with [JsonProperty("name")] if the JSON field names differ from your C# property names, and call DeserializeObject with the target type as the generic parameter. For example: string json = JsonConvert.SerializeObject(new {'{ name = "Alice", age = 30 }'}) produces {"name":"Alice","age":30}. For deserialization: var user = JsonConvert.DeserializeObject<User>(json) returns a typed User object. Both methods accept an optional JsonSerializerSettings parameter for fine-grained control over null handling, date formatting, camelCase conversion, and more than 30 other options.

What is the difference between JObject and DeserializeObject<T> in Newtonsoft.Json?

DeserializeObject<T> maps JSON directly to a known C# class at parse time — it is fast, type-safe, and ideal when you control the JSON structure. JObject.Parse(json) returns a dynamic JSON tree where you navigate fields with string indexers (jObj["key"]) without needing a predefined class — useful when the JSON schema is unknown, variable, or when you only need a few fields from a large document. JObject is part of the LINQ to JSON API and supports JSONPath queries via SelectToken("$.path") and SelectTokens("$.array[*].field"). Performance-wise, DeserializeObject<T> is faster for full deserialization because it maps directly to typed fields; JObject is roughly 20–30% slower for full traversal but faster when you only read 1–2 fields from a large object since it avoids allocating unused properties.

How do I rename properties in Newtonsoft.Json output?

Apply the [JsonProperty("desired_name")] attribute to any C# property to control its name in the serialized JSON output. For example, decorating public string FirstName {'{ get; set; }'} with [JsonProperty("first_name")] causes Newtonsoft.Json to use "first_name" in the JSON string rather than "FirstName". This works for both serialization (writing JSON) and deserialization (reading JSON). For global camelCase conversion without per-property attributes, set ContractResolver = new CamelCasePropertyNamesContractResolver() in JsonSerializerSettings — this converts all PascalCase C# property names to camelCase JSON keys automatically. For snake_case globally, use a SnakeCaseNamingStrategy: ContractResolver = new DefaultContractResolver {'{ NamingStrategy = new SnakeCaseNamingStrategy() }'}. Per-property [JsonProperty] attributes always take precedence over the global contract resolver.

How do I ignore null values when serializing with Newtonsoft.Json?

Pass a JsonSerializerSettings object with NullValueHandling = NullValueHandling.Ignore to JsonConvert.SerializeObject. This omits all null properties from the output JSON, which reduces payload size and prevents null keys from appearing in the response. Example: JsonConvert.SerializeObject(obj, new JsonSerializerSettings {'{ NullValueHandling = NullValueHandling.Ignore }'}). You can also control null handling per-property using the NullValueHandling parameter on [JsonProperty]: [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)]. The default value is NullValueHandling.Include, which writes null properties as "key": null. For ASP.NET Core applications, configure the global setting in services.AddControllers().AddNewtonsoftJson(o => o.SerializerSettings.NullValueHandling = NullValueHandling.Ignore).

How do I write a custom JsonConverter in Newtonsoft.Json?

Create a class that extends JsonConverter<T> and override WriteJson(JsonWriter, T, JsonSerializer) and ReadJson(JsonReader, Type, T, bool, JsonSerializer). In WriteJson, use writer.WriteStartObject(), writer.WritePropertyName("key"), and writer.WriteValue(val) to produce the desired JSON structure. In ReadJson, use JObject.Load(reader) to read the input and map it to the target type. Register the converter globally via JsonSerializerSettings.Converters.Add(new MyConverter()) or per-property with [JsonConverter(typeof(MyConverter))]. A common use case is polymorphic deserialization where a discriminator field determines which concrete subclass to instantiate — read the type field from a JObject, then call jObj.ToObject<ConcreteType>(serializer) based on its value. Override CanRead or CanWrite to return false for one-directional converters.

How do Newtonsoft.Json and System.Text.Json compare in performance?

System.Text.Json (built into .NET 5+) is 20–40% faster than Newtonsoft.Json for typical serialization workloads and uses significantly less memory because it avoids intermediate string allocations by writing directly to a Utf8JsonWriter. For a 1 KB JSON object, System.Text.Json serialization takes approximately 1.2 µs versus Newtonsoft.Json at 1.8 µs in .NET 8 benchmarks. System.Text.Json also supports source generation via JsonSerializerContext, which eliminates reflection entirely for AOT scenarios like Native AOT and Blazor WebAssembly. However, Newtonsoft.Json remains more feature-complete: it supports more than 30 JsonSerializerSettings options, handles circular references out of the box, and has broader community support for edge cases. For new .NET 6+ projects without legacy constraints, System.Text.Json is the recommended default; for projects relying on LINQ to JSON or TypeNameHandling polymorphism, Newtonsoft.Json remains the practical choice.

How do I deserialize a JSON array with Newtonsoft.Json?

Use JsonConvert.DeserializeObject<List<T>>(json) to deserialize a JSON array into a generic List<T>, or DeserializeObject<T[]>(json) for an array. For example, given the JSON [{'{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}'}], call var users = JsonConvert.DeserializeObject<List<User>>(json) to get a List<User> with 2 elements. If the JSON array contains mixed types or unknown objects, use DeserializeObject<List<JObject>>(json) to get a list of dynamic JObject instances. For arrays nested inside an object, define a C# class with a List<T> property and deserialize the outer object. JArray.Parse(json) is the LINQ to JSON equivalent — it returns a JArray where each element is a JToken, accessible by index (jArray[0]) or via LINQ operators like Where and Select.

How do I use JSONPath with Newtonsoft.Json SelectToken?

Call jObject.SelectToken("$.path.expression") on any JToken to evaluate a JSONPath expression and return the first matching token, or null if no match is found. SelectTokens("$.path") returns IEnumerable<JToken> with all matches. JSONPath syntax: $ refers to the root, . accesses a child property, [] accesses array elements by index, [*] selects all array elements, ..property performs a recursive descent search, and [?(@.field == value)] filters elements. For example, jObj.SelectToken("$.users[0].email") returns the email of the first user. jObj.SelectTokens("$.orders[*].total") returns all order totals as an IEnumerable<JToken>. Cast the result to the desired type with .Value<string>() or .ToObject<T>(). SelectToken throws JsonException if the path expression is malformed — wrap in try/catch for untrusted path strings.

Format and validate JSON online

Paste any JSON object into Jsonic's formatter to instantly validate and pretty-print it — no signup required.

Open JSON Formatter

Further reading and primary sources