Start Debugging

System.Text.Json no .NET 11 Preview 3 adiciona PascalCase e políticas de naming por membro

.NET 11 Preview 3 fecha a história de políticas de naming no System.Text.Json: JsonNamingPolicy.PascalCase, um atributo [JsonNamingPolicy] em nível de membro, e um default [JsonIgnore] em nível de tipo para DTOs mais limpos.

O .NET 8 introduziu o primeiro lote de políticas de naming embutidas para System.Text.Json: camel, snake e kebab em ambos os casings. O Preview 3 do .NET 11 fecha a última lacuna óbvia e adiciona mais dois botões que tornam JsonConverters artesanais desnecessários para a maioria dos formatos de DTO. O trabalho saiu via dotnet/runtime #124644, #124645 e #124646.

PascalCase entra para as políticas embutidas

JsonNamingPolicy.PascalCase é novo no Preview 3 e fica ao lado dos já existentes CamelCase, SnakeCaseLower, SnakeCaseUpper, KebabCaseLower e KebabCaseUpper. É a política que você quer quando o lado .NET já usa propriedades PascalCase e o contrato JSON também é PascalCase, comum para APIs de Azure Management, gateways antigos de SOAP para REST e alguns formatos do Microsoft Graph:

using System.Text.Json;

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.PascalCase
};

var json = JsonSerializer.Serialize(
    new { firstName = "Ada", age = 37 },
    options);
// {"FirstName":"Ada","Age":37}

Antes do Preview 3 você deixava o default (sem política) ou escrevia uma subclasse custom de JsonNamingPolicy de uma linha. Agora ela combina com os outros presets e faz round-trip limpo com o enum JsonKnownNamingPolicy existente.

Sobrescrevendo o naming num único membro

A mudança mais interessante é que [JsonNamingPolicy] agora é um atributo em nível de membro. Antes a política vivia em JsonSerializerOptions e se aplicava ao grafo inteiro, então uma exceção PascalCase num contrato que no mais era camelCase significava ou um override [JsonPropertyName] em cada propriedade esquisita ou uma política totalmente custom. No .NET 11 Preview 3 você consegue misturar políticas dentro do mesmo tipo:

using System.Text.Json.Serialization;

public sealed class Webhook
{
    public string Url { get; set; } = "";

    [JsonNamingPolicy(JsonKnownNamingPolicy.KebabCaseLower)]
    public string RetryStrategy { get; set; } = "exponential";

    [JsonNamingPolicy(JsonKnownNamingPolicy.SnakeCaseLower)]
    public int MaxAttempts { get; set; } = 5;
}

Com PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Url serializa para url, RetryStrategy para retry-strategy e MaxAttempts para max_attempts. Isso tira muito barulho de [JsonPropertyName] por propriedade quando um sistema externo único é inconsistente.

Defaults de [JsonIgnore] em nível de tipo

A mudança companheira é que [JsonIgnore(Condition = ...)] agora é legal no próprio tipo, não só em propriedades (dotnet/runtime #124646). Coloque na classe e a condição vira o default para cada propriedade dentro do tipo:

using System.Text.Json.Serialization;

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public sealed class PatchRequest
{
    public string? Name { get; set; }
    public string? Email { get; set; }

    [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
    public bool? IsActive { get; set; }
}

Cada propriedade nullable em PatchRequest agora some do payload quando é null, que é exatamente o que um formato de requisição JSON Merge Patch quer. O override de IsActive volta a entrar porque um false explícito é significativo ali. O mesmo padrão antes exigia JsonIgnoreCondition.WhenWritingNull em cada propriedade individualmente ou DefaultIgnoreCondition nas opções do serializer, o que depois forçava todo outro DTO pela mesma regra.

Por que a superfície pequena importa

Controle em nível de atributo é o que permite aos times substituir converters custom pelo System.Text.Json de prateleira. PascalCase remove a última razão para “escreva sua própria política”, naming por membro apaga uma classe de boilerplate de [JsonPropertyName], e [JsonIgnore] em nível de tipo deixa DTOs de PATCH e de eventos configurarem o default num lugar só. As três mudanças também funcionam com o source generator, então apps Native AOT as recebem sem configuração extra. As notas de libraries do Preview 3 rastreiam o resto das atualizações de System.Text.Json saindo este mês.

< Voltar