JSON Handling
JSON dominates web APIs.
Introduction
JSON dominates web APIs. System.Text.Json offers high performance, trim-friendly source generators, and tight ASP.NET Core integration. Master JsonSerializerOptions, custom converters, and JsonNode for dynamic parsing.
Interviewers ask camelCase vs PascalCase, DateTime serialization ISO 8601, and deserializing partial or dynamic JSON from third-party APIs.
The story
A customer portal API exposes strongly typed IDs in JSON as standard GUID strings, even though internally they are wrapped in a CustomerId value object. A custom JsonConverter controls exactly how that type appears on the wire — preventing accidental serialization of internal fields and keeping API contracts stable.
Understanding the topic
Key concepts
- JsonSerializer.Serialize/Deserialize generic API.
- JsonDocument/JsonElement read-only DOM parse.
- JsonNode mutable DOM (.NET 6+).
- JsonConverter
for custom types. - ReferenceHandler.IgnoreCycles for graphs.
- Minimal APIs bind JSON automatically.
Step-by-step explanation
- ASP.NET Core configures JSON options in Program.cs.
- PropertyNamingPolicy.CamelCase for API output.
- Converters handle Guid, enums, dates.
- JsonSerializerContext source gen for AOT.
- TryGetProperty on JsonElement for safe access.
- HttpClient GetFromJsonAsync extension deserializes.
Practical code example
Custom converter for strongly typed ID and HttpClient JSON GET:
namespace TechLearningPro.Json;public readonly record struct CustomerId(Guid Value){public static CustomerId New() => new(Guid.NewGuid());}public sealed class CustomerIdConverter : JsonConverter<CustomerId>{public override CustomerId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>new(Guid.Parse(reader.GetString()!));public override void Write(Utf8JsonWriter writer, CustomerId value, JsonSerializerOptions options) =>writer.WriteStringValue(value.Value.ToString("D"));}// Register in Program.cs: options.JsonSerializerOptions.Converters.Add(new CustomerIdConverter());
Line-by-line code explanation
readonly record struct CustomerId(Guid Value)wraps a GUID in a domain-friendly type.CustomerId.New() => new(Guid.NewGuid())factory method creates fresh IDs.JsonConverter<CustomerId>is the base class for custom serialization logic.Read(ref Utf8JsonReader reader, ...)controls deserialization from JSON tokens.Guid.Parse(reader.GetString()!)parses the string token into a GUID.new CustomerId(...)wraps the parsed GUID in the value object.Write(Utf8JsonWriter writer, CustomerId value, ...)controls JSON output format.writer.WriteStringValue(value.Value.ToString("D"))writes the canonical GUID string format.options.JsonSerializerOptions.Converters.Add(...)registers the converter globally in ASP.NET Core.
Key takeaway: Custom converters encapsulate serialization rules for value objects. Keeps domain types strong at API boundary.
Real-world use
Where you'll use this in production
- Third-party REST API integration.
- Webhook payload parsing with JsonDocument.
- Configuration appsettings.json binding.
- Redis cache JSON blobs.
Best practices
- Use ISO 8601 DateTimeOffset for timestamps.
- Validate JSON schema at API boundary.
- Prefer strongly typed DTOs over dynamic.
- Configure naming policy globally once.
- Use JsonIgnore on sensitive fields.
Common mistakes
- Case sensitivity mismatch property names.
- Deserializing large JSON to JsonDocument without dispose.
- Enum as string vs int mismatch clients.
- Infinite loop serializing parent-child graphs.
Advanced interview questions
Q1BeginnerSystem.Text.Json vs Newtonsoft?
Q2BeginnerParse JSON without full class?
Q3IntermediateCustom converter when?
Q4IntermediateIgnore reference cycles?
Q5AdvancedDeserialize polymorphic webhook events safely.
Summary
System.Text.Json powers ASP.NET Core JSON. Custom converters and options tailor serialization. JsonDocument/JsonNode for dynamic parsing. Strong DTOs beat dynamic at API boundaries. Next: delegates.