CodeGym /Kurse /C# SELF /Kontrolle über den Prozess mittels Attribute

Kontrolle über den Prozess mittels Attribute

C# SELF
Level 45 , Lektion 2
Verfügbar

1. Wofür braucht man Attribute bei der Serialisierung?

Wenn du Objekte in JSON oder XML serialisierst, passiert etwas Ähnliches wie wenn ein Saugroboter dein Zimmer scannt. Alles, was offen sichtbar ist (öffentliche Properties/Felder), landet im „Beutel“ (in der Datei oder im String), und der Rest wird ignoriert. Aber manchmal willst du nicht, dass der Roboter heimlich den Inhalt deiner Tasche zeigt, oder du willst Dinge anders benennen — zum Beispiel nicht auf Russisch, sondern auf Englisch.

Hier kommen Attribute ins Spiel! Sie erlauben es, den Serialisierungsprozess zu steuern: Überflüssiges zu verbergen, Namen zu ändern, Reihenfolgen festzulegen, einzelne Teile des Objekts zu ignorieren oder dem Serializer mitzuteilen, dass es spezielle Regeln gibt. Das ist wie Aufkleber an Sachen: „nicht anfassen“, „wichtig“, „in JSON umbenennen“.

Typische Aufgaben der Serialisierungskontrolle

  • Den Namen einer Property oder eines Feldes im Ausgabe­dokument ändern (z. B. im JSON statt FirstName "first_name" machen)
  • Einige Felder/Properties aus der Serialisierung oder Deserialisierung ausblenden (z. B. Passwörter, interne Zähler)
  • Die Behandlung von Default-Werten oder null-Werten steuern
  • Die Reihenfolge der Elemente festlegen (relevant für XML)
  • Zusätzliche Parameter (Attribute) für XML-Elemente beschreiben

Jede Serialisierungsplattform — sei es System.Text.Json, Newtonsoft.Json oder XmlSerializer — verwendet dafür eigene Attribute.

2. Attribute für System.Text.Json: modern und hip

Der klassische JSON-Serializer im .NET-System bringt eine ziemlich nützliche Liste von Attributen mit, die im Namespace System.Text.Json.Serialization leben.

Die nützlichsten Attribute:

Attribut Wofür es gut ist Beispiel
JsonPropertyName("...")
Wandelt den Property-Namen im JSON um
[JsonPropertyName("id")]
JsonIgnore
Schließt die Property komplett aus der Serialisierung aus
[JsonIgnore]
JsonInclude
Serialisiert ein öffentliches Feld (nicht nur Property)
[JsonInclude]
JsonIgnore(Condition = ...)
Ignoriert die Property unter bestimmten Bedingungen
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

Beispiele:

using System.Text.Json.Serialization;

public class Person
{
    [JsonPropertyName("first_name")]
    public string FirstName { get; set; } // Der Name im JSON wird 'first_name' sein

    [JsonIgnore]
    public string Password { get; set; } // Kommt nicht ins JSON

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

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string Nickname { get; set; } // Wenn null — kommt nicht ins JSON
}

Lass uns so eine Person serialisieren:

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

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

Beachte: sowohl das Passwort als auch der Spitzname sind aus dem JSON verschwunden! (Passwort — weil es immer ignoriert wird, Spitzname — nur wenn es null ist).

Warum ist das wichtig? In der Praxis willst du oft nicht, dass die Client-Seite (oder ein Angreifer!) kritische Daten wie Passwörter, Tokens, Zähler oder interne Timestamps sieht. Mit [JsonIgnore] und ähnlichen Attributen geht das in einer Zeile.

3. Attribute für Newtonsoft.Json: der Champion der Flexibilität

Wenn du mit Newtonsoft.Json arbeitest (Dokumentation hier), hast du noch mehr Möglichkeiten (und es ist manchmal einfacher, wenn du an ältere Projekte gewöhnt bist).

Wichtige Attribute:

Attribut Verwendung
JsonProperty("...")
Setzt den Namen im JSON, sowie Order, Required usw.
JsonIgnore
Schließt Feld/Property aus der Serialisierung aus
JsonRequired
Erfordert das Vorhandensein beim Deserialisieren
JsonConverter
Erlaubt einen eigenen Converter für komplexe Fälle anzugeben
DefaultValueHandling
Steuert die Serialisierung von Default-Werten

Praktisches Beispiel:

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

Zusätzliche Möglichkeiten von JsonProperty — Required (Required), Order (Order) usw. Zum Beispiel:

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

4. Attribute für XML: klassischer Stil

XmlSerializer nutzt einen ganzen Werkzeugkasten eigener Attribute aus dem Namespace System.Xml.Serialization.

Am häufigsten verwendete:

Attribut Wofür es ist
[XmlElement("...")]
Element im XML mit anderem Namen
[XmlAttribute("...")]
Wandelt die Property in ein Attribut des XML-Elements
[XmlIgnore]
Schließt Property oder Feld aus der XML-Serialisierung aus
[XmlArray("...")]
Für Collections — setzt den Namen des XML-Arrays
[XmlArrayItem("...")]
Für Collections — setzt den Namen des Array-Elements
[XmlRoot("...")]
Ändert den Namen des Wurzel-XML-Tags

Beispiel:

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

Nach der Serialisierung sieht das XML ungefähr so aus:

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

Beachte, dass das Feld Secret nicht ins XML gekommen ist, und Age als Attribut serialisiert wurde, nicht als verschachtelter Tag.

5. Nützliche Feinheiten

Unter der Haube: wie Attribute funktionieren

Wenn der Serializer deine Klasse sieht, „liest“ er sie praktisch per Reflection (die .NET-Magie, mehr dazu in System.Reflection). Der Serializer liest die Metadaten: z. B. ob eine Property das Attribut JsonIgnore oder XmlElement hat. Je nachdem fügt er Daten ins Enddokument ein (oder überspringt sie).

Das ist ein praktischer Weg, die „Daten-Schema“-Sicht von der Business-Logik zu trennen. Deine Klasse ist deine Business-Logik, und Attribute sind quasi der Ausweis für die Serialisierung.

Tabelle der Entsprechungen wichtiger Attribute

Funktion System.Text.Json Newtonsoft.Json XmlSerializer
Name ändern
[JsonPropertyName]
[JsonProperty]
[XmlElement]
[XmlAttribute]
Ignorieren
[JsonIgnore]
[JsonIgnore]
[XmlIgnore]
Custom-Format
[JsonConverter]
[JsonConverter]
— (über IXmlSerializable, schmerzhaft)
Root-Objekt
[XmlRoot]
Collection
[XmlArray]
[XmlArrayItem]

6. Fortgeschrittene Szenarien

Manchmal musst du ein Objekt nicht so serialisieren, wie es der Standard-Serializer tut. Zum Beispiel willst du ein Datum als UNIX-Timestamp speichern, statt im ISO-Format. Dafür gibt es Attribute, mit denen du eigene Converter anschließen kannst.

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

Jetzt wird beim Serialisieren das Feld EventTime zu einer Zahl statt zu einem Datums-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; }
}

Nun wird ein Boolean als "yes" oder "no" serialisiert und beim Deserialisieren wieder zu true oder false.

7. Besonderheiten und Fallstricke

Wenn du Attribute hinzufügst, denk dran: verschiedene Serializer nutzen verschiedene Attribute. Wenn du mit JSON und XML arbeitest (oder sogar gleichzeitig mit Newtonsoft.Json und System.Text.Json), vergiss nicht, beide nötigen Attribute zu setzen, sonst gibt es Überraschungen.

Der Standardname der Property wird vom Serializer genommen, sofern du ihn nicht per Attribut überschreibst.

Sei vorsichtig bei Vererbung: Unterklassen erben öffentliche Felder/Properties und wenn das Basisklasse-Element ein Attribut hatte, gilt es auch für die Unterklasse. Das überrascht oft, ist aber so beabsichtigt.

Typischer Fehler: Sehr häufig markieren Entwickler versehentlich ein Feld mit JsonIgnore, das unbedingt serialisiert werden muss (oder sie vergessen sensitive Daten zu ignorieren). Oder sie setzen zum Beispiel XmlElement an ein privates Feld — und wundern sich, warum der Serializer es nicht sieht (XmlSerializer verarbeitet nur public Members!).

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION