CodeGym /Courses /C# SELF /Control over the process via attributes

Control over the process via attributes

C# SELF
Level 45 , Lesson 2
Available

1. Why use attributes for serialization?

When you serialize objects to JSON or XML, something like a vacuum robot scans your room. Everything that's clearly visible (public properties/fields) goes into the "bag" (file or string), and the rest is ignored. But sometimes you don't want the robot to casually show the contents of your backpack, or you want to rename things — e.g., not in Russian, but in English.

That's where attributes come in! They let you control the serialization process: hide extras, change names, set ordering, ignore parts of an object, or tell the serializer you have special rules. It's like stickers on items: "do not touch", "important", "rename in JSON".

Typical serialization control tasks

  • Change the name of a property or field in the output document (for example, in JSON instead of FirstName make "first_name")
  • Hide some fields/properties from serialization or deserialization (for example, passwords, internal counters)
  • Control handling of default values or null values
  • Set the order of elements (relevant for XML)
  • Describe additional parameters (attributes) for XML elements

Each serialization platform — whether System.Text.Json, Newtonsoft.Json or XmlSerializer — uses its own attributes for these purposes.

2. Attributes for System.Text.Json: modern and trendy

The classic JSON serializer from the .NET system supports a decent list of attributes that live in the namespace System.Text.Json.Serialization.

The most useful attributes:

Attribute What it's for Usage example
JsonPropertyName("...")
Converts the property name in JSON
[JsonPropertyName("id")]
JsonIgnore
Completely excludes the property from serialization
[JsonIgnore]
JsonInclude
Serializes a public field (not only a property)
[JsonInclude]
JsonIgnore(Condition = ...)
Ignore a property under certain conditions
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

Usage examples:

using System.Text.Json.Serialization;

public class Person
{
    [JsonPropertyName("first_name")]
    public string FirstName { get; set; } // The name in JSON will be 'first_name'

    [JsonIgnore]
    public string Password { get; set; } // Won't appear in JSON

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

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string Nickname { get; set; } // If null — won't appear in JSON
}

Let's serialize such a person:

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

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

Note: both the password and the nickname disappeared from the JSON! (Password — because it's always ignored, and nickname — only if it's null).

Why this matters? In practice you often don't want the client side (or an attacker!) to see critical data like passwords, tokens, counters, internal timestamps. With [JsonIgnore] and similar attributes you can do this in one line.

3. Attributes for Newtonsoft.Json: the flagship of flexibility

If you work with Newtonsoft.Json (docs here), you'll get even more options (and a bit simpler if you're used to older projects).

Main attributes:

Attribute Purpose
JsonProperty("...")
Sets the property name in JSON, and also order, requiredness, etc.
JsonIgnore
Excludes a field/property from serialization
JsonRequired
Requires presence during deserialization
JsonConverter
Allows specifying a custom converter for complex cases
DefaultValueHandling
Controls serialization of default values

Practical example:

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; }
}

Additional features of JsonProperty — requiredness (Required), order (Order) and so on. For example:

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

4. Attributes for XML: the "classic" style

XmlSerializer uses a whole arsenal of its own attributes from the namespace System.Xml.Serialization.

Most common ones:

Attribute What it's for
[XmlElement("...")]
Element in XML with a different name
[XmlAttribute("...")]
Converts a property to an XML element attribute
[XmlIgnore]
Excludes a property or field from XML serialization
[XmlArray("...")]
For collections — sets the XML array name
[XmlArrayItem("...")]
For collections — sets the array item name
[XmlRoot("...")]
Changes the root XML tag name

Practical example:

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" };

After serialization the XML will look approximately like this:

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

Note that the Secret field did not get into the XML, and Age was serialized as an attribute, not as a nested tag.

5. Useful nuances

Under the hood: how attributes work

When a serializer encounters your class, it literally "reads" it via reflection (the .NET magic, see more at System.Reflection). The serializer reads metadata: for example, whether a property has the JsonIgnore or XmlElement attribute. Depending on that it adds data to the final document (or skips it).

This is a convenient way to separate "data schema" from business logic. Your class is your business logic, and attributes are essentially the passport for serialization.

Mapping table of key attributes

Function System.Text.Json Newtonsoft.Json XmlSerializer
Change name
[JsonPropertyName]
[JsonProperty]
[XmlElement]
[XmlAttribute]
Ignore
[JsonIgnore]
[JsonIgnore]
[XmlIgnore]
Custom format
[JsonConverter]
[JsonConverter]
— (via IXmlSerializable, painful)
Object root
[XmlRoot]
Collection
[XmlArray]
[XmlArrayItem]

6. Advanced scenarios

Sometimes you need to serialize an object in a way the standard serializer doesn't support. For example, you want to store a date as a UNIX timestamp instead of the standard ISO format. For that case there are attributes that allow you to plug in custom converters.

In 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; }
}

Now when serializing the EventTime field it will become a number, not a date string.

In 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; }
}

Now a boolean value serializes to "yes" or "no", and back to true or false.

7. Peculiarities and pitfalls

When you add attributes, remember: different serializers use different attributes. If you work with both JSON and XML (or, in the worst case, with Newtonsoft.Json and System.Text.Json at the same time), don't forget to put both necessary attributes, otherwise you'll get a surprise.

The default property name will be taken by the serializer unless you override it via an attribute.

Be careful with inheritance: derived classes inherit public fields/properties and, if the parent had an attribute, it will apply in the derived class too. That often causes surprises, but it's by design.

Typical mistake: A common situation is when developers accidentally mark a field that should definitely be serialized with JsonIgnore (or, conversely, forget to ignore sensitive data). Or, for example, they put XmlElement on a private field — and wonder why the serializer doesn't see it (XmlSerializer only handles public members!).

2
Task
C# SELF, level 45, lesson 2
Locked
Ignoring null values
Ignoring null values
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION