CodeGym /Cursos /C# SELF /Controle do processo via atributos

Controle do processo via atributos

C# SELF
Nível 45 , Lição 2
Disponível

1. Para que servem os atributos na serialização?

Quando você serializa objetos para JSON ou XML, acontece algo parecido com um robô aspirador vasculhando seu quarto. Tudo que está visível (propriedades/fields públicos) vai para o "saco" (arquivo ou string), e o resto é ignorado. Mas às vezes você não quer que o robô exponha o conteúdo da sua mochila, ou quer dar nomes diferentes às coisas — por exemplo, não em russo, mas em inglês.

Aí entram os atributos! Eles deixam você controlar o processo de serialização: ocultar coisas, mudar nomes, definir ordem, ignorar partes do objeto ou dizer ao serializer que existem regras especiais. É como colocar adesivos nos itens: "não mexer", "importante", "renomear no JSON".

Tarefas típicas de controle de serialização

  • Mudar o nome de uma propriedade ou campo no documento de saída (por exemplo, no JSON em vez de FirstName usar "first_name")
  • Ocultar alguns campos/propriedades da serialização ou desserialização (por exemplo, senhas, contadores internos)
  • Controlar tratamento de valores padrão ou null
  • Definir a ordem dos elementos (relevante para XML)
  • Descrever parâmetros adicionais (atributos) para elementos XML

Cada plataforma de serialização — seja System.Text.Json, Newtonsoft.Json ou XmlSerializer — usa seus próprios atributos para esses fins.

2. Atributos para System.Text.Json: moderno e em alta

O serializer JSON padrão do .NET tem uma boa lista de atributos que vivem no namespace System.Text.Json.Serialization.

Os atributos mais úteis:

Atributo Para que serve Exemplo de uso
JsonPropertyName("...")
Converte o nome da propriedade no JSON
[JsonPropertyName("id")]
JsonIgnore
Exclui totalmente a propriedade da serialização
[JsonIgnore]
JsonInclude
Serializa um field público (e não só propriedade)
[JsonInclude]
JsonIgnore(Condition = ...)
Ignorar a propriedade sob certas condições
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

Exemplos de uso:

using System.Text.Json.Serialization;

public class Person
{
    [JsonPropertyName("first_name")]
    public string FirstName { get; set; } // O nome no JSON será 'first_name'

    [JsonIgnore]
    public string Password { get; set; } // Não vai para o JSON

    [JsonPropertyName("born_year")]
    public int? YearOfBirth { get; set; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string Nickname { get; set; } // Se for null — não entra no JSON
}

Vamos serializar essa pessoa:

var person = new Person
{
    FirstName = "Ivan",
    Password = "123456",
    YearOfBirth = 2000,
    Nickname = null
};

string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}

Repare: tanto a senha quanto o apelido sumiram do JSON! (Senha porque é sempre ignorada, apelido porque é null).

Por que isso é importante? Na prática, você quase sempre não quer que o cliente (ou um atacante!) veja dados críticos como senhas, tokens, contadores ou timestamps internos. Com [JsonIgnore] e afins isso se resolve numa linha.

3. Atributos para Newtonsoft.Json: o rei da flexibilidade

Se você usa Newtonsoft.Json (documentação aqui), as possibilidades são ainda maiores (e às vezes mais fáceis se você vem de projetos antigos).

Principais atributos:

Atributo Finalidade
JsonProperty("...")
Define o nome no JSON, além de ordem, obrigatoriedade, etc.
JsonIgnore
Exclui field/propriedade da serialização
JsonRequired
Exige presença obrigatória na desserialização
JsonConverter
Pode indicar um converter customizado para casos complexos
DefaultValueHandling
Controla serialização de valores padrão

Exemplo prático:

using Newtonsoft.Json;

public class UserProfile
{
    [JsonProperty("login")]
    public string Username { get; set; }

    [JsonIgnore]
    public string InternalNotes { get; set; }

    [JsonProperty(Required = Required.Always)]
    public string Email { get; set; }
}

Recursos extras do JsonProperty — obrigatoriedade (Required), ordem (Order) etc. Por exemplo:

[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }

4. Atributos para XML: estilo "classic"

XmlSerializer usa um arsenal de atributos do namespace System.Xml.Serialization.

Mais comuns:

Atributo Para que serve
[XmlElement("...")]
Elemento no XML com outro nome
[XmlAttribute("...")]
Converte a propriedade em um atributo do elemento XML
[XmlIgnore]
Exclui propriedade/field da serialização XML
[XmlArray("...")]
Para coleções — define o nome do array XML
[XmlArrayItem("...")]
Para coleções — define o nome do item do array
[XmlRoot("...")]
Muda o nome do tag raiz XML

Exemplo prático:

using System.Xml.Serialization;

[XmlRoot("human")]
public class Person
{
    [XmlElement("firstname")]
    public string Name { get; set; }

    [XmlAttribute("years")]
    public int Age { get; set; }

    [XmlIgnore]
    public string Secret { get; set; }
}

var person = new Person { Name = "Anna", Age = 32, Secret = "42" };

Depois da serialização, o XML ficaria mais ou menos assim:

<human years="32"><firstname>Anna</firstname></human>

Note que o field Secret não entrou no XML, e Age foi serializado como atributo, não como tag filho.

5. Dicas úteis

Por baixo do capô: como os atributos funcionam

Quando o serializer encontra sua classe, ele literalmente "lê" via reflection (mágica do .NET, veja System.Reflection). O serializer consulta metadados: por exemplo, existe o atributo JsonIgnore ou XmlElement na propriedade? Dependendo disso, ele inclui ou pula dados no documento final.

É uma maneira prática de separar "esquema de dados" da lógica de negócio. Sua classe é lógica de negócio, e os atributos são, na prática, o passaporte para a serialização.

Tabela de correspondência dos atributos principais

Função System.Text.Json Newtonsoft.Json XmlSerializer
Mudar nome
[JsonPropertyName]
[JsonProperty]
[XmlElement]
[XmlAttribute]
Ignorar
[JsonIgnore]
[JsonIgnore]
[XmlIgnore]
Formato custom
[JsonConverter]
[JsonConverter]
— (via IXmlSerializable, é dolorido)
Raiz do objeto
[XmlRoot]
Coleção
[XmlArray]
[XmlArrayItem]

6. Cenários avançados

Às vezes você precisa serializar um objeto de um jeito que o serializer padrão não faz. Por exemplo, guardar uma data como UNIX timestamp em vez do ISO padrão. Para isso existem atributos que permitem ligar converters personalizados.

No System.Text.Json:

public class UnixDateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime;

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        => writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds());
}

public class LogEntry
{
    [JsonConverter(typeof(UnixDateTimeConverter))]
    public DateTime EventTime { get; set; }
}

Agora, ao serializar, o campo EventTime vira um número em vez de uma string de data.

No Newtonsoft.Json:

public class BoolToYesNoConverter : JsonConverter<bool>
{
    public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
        => writer.WriteValue(value ? "yes" : "no");

    public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
        => (string)reader.Value == "yes";
}

public class Answer
{
    [JsonConverter(typeof(BoolToYesNoConverter))]
    public bool IsCorrect { get; set; }
}

Agora um booleano é serializado como "yes" ou "no", e ao desserializar volta para true ou false.

7. Peculiaridades e armadilhas

Ao adicionar atributos, lembre-se: diferentes serializers usam atributos diferentes. Se você trabalha com JSON e XML (ou até com Newtonsoft.Json e System.Text.Json ao mesmo tempo), não esqueça de colocar ambos os atributos necessários, senão vem surpresa.

O nome padrão da propriedade será usado pelo serializer, a menos que você o override com um atributo.

Cuidado com herança: classes filhas herdam fields/propriedades públicas e, se o atributo estava no pai, ele também se aplica na criança. Isso costuma causar surpresa, mas é por design.

Erro típico: É comum desenvolvedores marcarem acidentalmente um campo que deveria entrar na serialização com JsonIgnore (ou esquecer de ignorar dados sensíveis). Ou então colocar XmlElement em um field privado — e depois estranhar por que o serializer não vê aquilo (XmlSerializer só processa membros public!).

2
Tarefa
C# SELF, nível 45, lição 2
Bloqueado
Ignorando valores nulos
Ignorando valores nulos
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION