Skip to Content
Course content

206: JsonSerializer Options and Attributes

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start working with System.Text.Json, it feels like magic. You pass a class into the serializer, and it spits out a string. But that magic breaks the moment you have to integrate with a real-world API. Most external APIs use snake_case or camelCase, while we, as C# developers, adhere to PascalCase for our properties. This creates a friction point that many developers handle the wrong way.

The temptation to break C# naming conventions

I've seen a lot of juniors solve this by simply naming their C# properties to match the JSON. They'll write a class like this:

public class UserProfile
{
    public string first_name { get; set; }
    public string last_name { get; set; }
    public string email_address { get; set; }
}

I'll be honest: it works. The JSON matches perfectly. But you've just poisoned your codebase. Now, every time you use this object in your business logic, you're writing user.first_name instead of user.FirstName. You're fighting the language and your IDE's static analysis just to satisfy a JSON string. It's a short-term win that creates long-term technical debt. You should never let an external data format dictate your internal C# naming standards.

Solving the bulk of the problem with global options

The better way to handle this is to decouple your C# names from the JSON keys using JsonSerializerOptions. If your API is consistently using camelCase, you don't need to touch your classes at all. You just tell the serializer how to behave globally.

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};

string json = JsonSerializer.Serialize(user, options);

This is the "clean" approach. Your C# properties stay PascalCase, but the output becomes camelCase. It's a single line of configuration that handles every property in your object graph. However, this approach breaks down the moment you hit a "special" field. What happens when the API uses user_id (snake_case) but the rest is camelCase? Or what if a field is named id in JSON but you want it called InternalAccountId in your code for clarity?

Precision control with attributes

This is where attributes come in. Think of JsonSerializerOptions as the broad brush and attributes as the scalpel. By using [JsonPropertyName], you can override the global policy for a specific property. This allows you to maintain a beautiful C# domain model while remaining perfectly compatible with a messy API.

public class UserProfile
{
    [JsonPropertyName("user_id")]
    public int UserId { get; set; }

    [JsonPropertyName("first_name")]
    public string FirstName { get; set; }

    public string Email { get; set; } // Will follow the global PropertyNamingPolicy
}

I usually recommend using global options for the 90% use case and attributes for the exceptions. It keeps your configuration lean and your intentions explicit. While you're at it, you'll likely encounter data you don't want to serialize—like a password hash or a sensitive internal state. Instead of making that property internal or private (which can complicate your constructors), use [JsonIgnore]. It tells the serializer to completely skip that property, ensuring you don't accidentally leak secrets to a client-side application.

The cost of flexibility

There is a small trade-off here: tight coupling. When you use [JsonPropertyName], your C# class now "knows" about the JSON format. If the API changes a key from first_name to given_name, you have to update your code and recompile. For most applications, this is acceptable. If you're building something that needs to be extremely dynamic—where the keys change based on user configuration—you'd need to move toward a Dictionary<string, object> or a custom JsonConverter, but that's a level of complexity you should avoid until you actually need it.




📋 Practical Task

Refactoring the Legacy Product Catalog

You have been handed a Product class that was written by a developer who ignored C# naming conventions to make the JSON "just work." Your task is to refactor this class and the serialization logic to follow professional standards.

The current (bad) code:

public class Product
{
    public string product_sku { get; set; }
    public string display_name { get; set; }
    public decimal unit_price { get; set; }
    public string internal_warehouse_code { get; set; }
}

// Usage
var product = new Product { product_sku = "ABC-123", display_name = "Widget", unit_price = 10.99m, internal_warehouse_code = "WH-01" };
string json = JsonSerializer.Serialize(product);

Requirements:

  • Rename all properties to PascalCase (e.g., ProductSku).
  • Use [JsonPropertyName] to ensure the JSON output still uses the snake_case keys (e.g., product_sku).
  • The internal_warehouse_code property must be completely omitted from the resulting JSON string using the appropriate attribute.
  • Configure JsonSerializerOptions to enable WriteIndented = true so the output is human-readable.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.