JSON for .NET: A Practical Guide for Developers
JSON is the de facto format for data interchange in modern web and cloud applications. In .NET, working with JSON is straightforward thanks to a mature ecosystem of libraries and built-in support. This guide covers practical techniques for serializing and deserializing JSON, choosing libraries, handling common pitfalls, and optimizing for performance and compatibility.
When to use JSON in .NET
- Exchanging data between services (REST APIs, microservices).
- Persisting lightweight configuration or cached data.
- Communicating with JavaScript frontends or third-party APIs.
- Logging structured events for observability.
Key libraries and when to pick them
| Library | When to use |
|---|---|
| System.Text.Json (built-in) | Default choice for .NET Core/.NET 5+ — fast, low allocations, actively improved. |
| Newtonsoft.Json (Json.NET) | Use for legacy projects, advanced features (TypeNameHandling, flexible converters), or when third-party dependencies require it. |
| Utf8Json, Jil (third-party alternatives) | Consider for extreme performance scenarios (benchmarks may vary). |
Basic serialization and deserialization
System.Text.Json (recommended default)
csharp
using System.Text.Json; var obj = new Person { Name = “Alice”, Age = 30 }; string json = JsonSerializer.Serialize(obj); Person read = JsonSerializer.Deserialize<Person>(json);
Newtonsoft.Json
csharp
using Newtonsoft.Json; string json = JsonConvert.SerializeObject(obj); Person read = JsonConvert.DeserializeObject<Person>(json);
Common scenarios and solutions
- Custom property names and casing
- System.Text.Json: use [JsonPropertyName]
csharp
public class Person { [JsonPropertyName(“full_name”)] public string Name { get; set; } }
- Newtonsoft.Json: use [JsonProperty(“fullname”)]
- Ignoring properties
- System.Text.Json: [JsonIgnore]
- Newtonsoft.Json: [JsonIgnore]
- Null handling and default values
- System.Text.Json: configure JsonSerializerOptions: DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
- Newtonsoft.Json: NullValueHandling = NullValueHandling.Ignore
- Polymorphic types
- System.Text.Json: limited built-in support; implement custom JsonConverter or use JsonSerializerOptions.Converters with a type discriminator pattern.
- Newtonsoft.Json: use TypeNameHandling and TypeNameAssemblyFormat (be cautious: security risks with TypeNameHandling.Auto when deserializing untrusted input).
- Custom converters
- System.Text.Json: inherit from JsonConverter and register in JsonSerializerOptions.Converters.
- Newtonsoft.Json: inherit from JsonConverter and add via JsonSerializerSettings.Converters.
- Streaming large payloads
- Use async APIs and Utf8JsonReader/Utf8JsonWriter (System.Text.Json) or JsonTextReader/JsonTextWriter (Newtonsoft) to process data without loading entire payloads into memory.
- Working with DOM-like structures
- System.Text.Json: JsonDocument and JsonElement for read-only traversal; JsonNode/JsonObject for mutable DOM (in newer runtime versions).
- Newtonsoft.Json: JObject/JToken for flexible, mutable DOM-style manipulation.
Performance tips
- Prefer System.Text.Json for most workloads; it’s optimized in recent .NET versions.
- Reuse JsonSerializerOptions instances (they are thread-safe for reuse) to avoid allocation cost.
- Avoid boxing/unboxing of value types in converters.
- Use UTF-8 APIs when working with byte arrays or streams to avoid extra encoding steps.
- For high-throughput scenarios, benchmark with representative data and consider span-based APIs such as Utf8JsonReader.
Security considerations
- Never deserialize untrusted JSON into types with automatic type name handling (avoid TypeNameHandling with Newtonsoft unless input is trusted).
- Validate or sanitize incoming data shapes and lengths to prevent resource exhaustion.
- Limit depth and size when reading JSON to avoid stack overflows or excessive memory usage.
Interoperability tips
- Align naming policies between backend and frontend (camelCase is common for JavaScript).
- Use explicit versioning of API payloads if message formats may evolve.
- Use nullable reference types and optional properties to make breaking changes safer.
Example: robust configuration for System.Text.Json
csharp
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, WriteIndented = false, }; options.Converters.Add(new DateTimeConverterUsingIso8601()); string json = JsonSerializer.Serialize(obj, options); var obj2 = JsonSerializer.Deserialize<MyType>(json, options);
Troubleshooting checklist
- Unexpected property names: check naming policy and attributes.
- Missing properties: ensure properties are public with get/set and not ignored.
- Date/time problems: prefer ISO 8601 strings or custom converters; beware of time zone differences.
- Precision loss for numbers: use decimal for monetary values and check JSON number handling.
Summary
- Use System.Text.Json as the default for new .NET projects for speed and low allocation.
- Use Newtonsoft.Json when you need advanced features or compatibility.
- Prefer explicit converters and naming policies to avoid surprises.
- Stream and process large payloads to reduce memory usage.
- Apply security best practices when deserializing untrusted input.
Leave a Reply