CodeGym /Kurse /C# SELF /Arbeiten mit verschachtelten Objekten und Kollektionen

Arbeiten mit verschachtelten Objekten und Kollektionen

C# SELF
Level 45 , Lektion 1
Verfügbar

1. Einführung

Die Serialisierung einfacher Objekte ist wie eine Postkarte verschicken: ganz einfach, nichts geht verloren. Aber oft werden unsere Objekte zu "Familienalben": sie enthalten verschachtelte Objekte, Kollektionen und sogar Kollektionen von Kollektionen.

Stell dir vor, unser Benutzer ist nicht nur ein Person mit Name und Alter, sondern hat z.B. eine Menge Kontakte (Contact), mehrere private/Arbeitsadressen und vielleicht ein Feld vom Typ List<Pet>, wenn die Person Tiere mag. All das sind verschachtelte Objekte und Kollektionen.

Wie gehen die Serializer in .NET damit um? Welche Feinheiten sollte man beachten, wenn wir komplexe Objektbäume serialisieren? Kann alles kaputtgehen, wenn in den Kollektionen wieder Kollektionen stecken? Heute klären wir, was zu erwarten ist — und was in komplizierten Fällen zu tun ist.

Was "Verschachtelung" in .NET bedeutet

Ein verschachteltes Objekt ist einfach ein weiteres Objekt als Property oder Feld innerhalb deines Hauptobjekts. Zum Beispiel unser erweitertes Benutzer-Modell:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public List<Contact> Contacts { get; set; }         // Verschachtelte Objekt-Kollektion
    public Address? HomeAddress { get; set; }           // Ein verschachteltes Objekt (nullable)
}
public class Contact
{
    public string Type { get; set; }                    // Zum Beispiel Email oder Phone
    public string Value { get; set; }
}
public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

Beachte — eine Person kann mehrere Kontakte haben, die Adresse ist nur eine (und kann null sein). Das ist Klassiker bei vielen Modellen.

2. Serialisierung von Objekten und Kollektionen mit System.Text.Json

Einfaches Beispiel: eine Kollektion serialisieren und deserialisieren

Lass uns ansehen, wie das in der Praxis aussieht:

using System.Text.Json;

var person = new Person
{
    Name = "Anna",
    Age = 28,
    Contacts = new List<Contact>
    {
        new Contact { Type = "Email", Value = "anna@example.com" },
        new Contact { Type = "Phone", Value = "+1234567890" }
    },
    HomeAddress = new Address { City = "Berlin", Street = "Alexanderplatz, 1" }
};

string json = JsonSerializer.Serialize(person, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

Das Ergebnis sieht ungefähr so aus:

{
  "Name": "Anna",
  "Age": 28,
  "Contacts": [
    {
      "Type": "Email",
      "Value": "anna@example.com"
    },
    {
      "Type": "Phone",
      "Value": "+1234567890"
    }
  ],
  "HomeAddress": {
    "City": "Berlin",
    "Street": "Alexanderplatz, 1"
  }
}

Der Serializer versteht Verschachtelungen problemlos. Ist eine Property ein anderes Objekt, serialisiert er es "eingebettet". Ist es eine Liste, wird sie als Array serialisiert.

Fakt: Diese Serialisierung funktioniert automatisch für alle Kollektionen und verschachtelten Objekte, solange sie public sind und public Getter und Setter haben (get/set).

Deserialisierung: funktioniert "out of the box"

string jsonInput = /* JSON, das wir gerade bekommen haben */;
Person deserializedPerson = JsonSerializer.Deserialize<Person>(jsonInput);
Console.WriteLine(deserializedPerson.Name); // "Anna"
Console.WriteLine(deserializedPerson.Contacts[0].Type); // "Email"

Alles funktioniert — die Kontakte werden aus dem JSON-Array zurück in eine List<Contact> verwandelt, HomeAddress zurück in ein Address-Objekt.

3. Wie Kollektionen serialisiert werden: List, Arrays, Dictionary

Oft gibt es in deinem Modell nicht nur Listen (List<T>), sondern auch Dictionaries (Dictionary<TKey, TValue>) oder mehrdimensionale Arrays.

Beispiel mit einem Array

public class Team
{
    public string Name { get; set; }
    public Person[] Members { get; set; }
}

var team = new Team
{
    Name = "Entwickler",
    Members = new[]
    {
        new Person { Name = "Aleksei", Age = 31 },
        new Person { Name = "Ekaterina", Age = 27 }
    }
};
string json = JsonSerializer.Serialize(team, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

JSON:

{
  "Name": "Entwickler",
  "Members": [
    {
      "Name": "Aleksei",
      "Age": 31,
      "Contacts": null,
      "HomeAddress": null
    },
    {
      "Name": "Ekaterina",
      "Age": 27,
      "Contacts": null,
      "HomeAddress": null
    }
  ]
}

Die Serialisierung einer Kollektion ähnelt dem Verpacken vieler gleicher Postkarten: jedes Element ist eine einzelne "Postkarte".

Beispiel mit Dictionary

public class Phonebook
{
    public Dictionary<string, string> Phones { get; set; }
}

var phonebook = new Phonebook
{
    Phones = new Dictionary<string, string>
    {
        { "Andrei", "+12998887766" },
        { "Maria", "+12882223344" }
    }
};

string json = JsonSerializer.Serialize(phonebook, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

JSON:

{
  "Phones": {
    "Andrei": "+12998887766",
    "Maria": "+12882223344"
  }
}

In JSON wird ein Dictionary zu einem Objekt mit dynamischen Keys.

4. Verschachtelte Kollektionen (List innerhalb List, Arrays von Arrays)

.NET kann auch solche "Matroschkas" serialisieren:

public class Zoo
{
    public List<List<Animal>> AnimalGroups { get; set; }
}
public class Animal { public string Name { get; set; } }

var zoo = new Zoo
{
    AnimalGroups = new List<List<Animal>>
    {
        new List<Animal> { new Animal { Name = "Löwe" }, new Animal { Name = "Tiger" } },
        new List<Animal> { new Animal { Name = "Bär" } }
    }
};

string json = JsonSerializer.Serialize(zoo, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

JSON:

{
  "AnimalGroups": [
    [ { "Name": "Löwe" }, { "Name": "Tiger" } ],
    [ { "Name": "Bär" } ]
  ]
}

Das gleiche gilt auch für die Deserialisierung.

5. Besonderheiten der Serialisierung verschachtelter Objekte mit XmlSerializer

Beispiel: verschachteltes Objekt serialisieren

using System.Xml.Serialization;
using System.IO;

var person = new Person
{
    Name = "Ivan",
    Age = 35,
    HomeAddress = new Address { City = "Bonn", Street = "Beethoven str., 100" },
    Contacts = new List<Contact>
    {
        new Contact { Type = "Email", Value = "ivan@domain.de" }
    }
};

var serializer = new XmlSerializer(typeof(Person));
using var writer = new StringWriter();
serializer.Serialize(writer, person);
Console.WriteLine(writer.ToString());

Ergebnis:

<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Ivan</Name>
  <Age>35</Age>
  <Contacts>
    <Contact>
      <Type>Email</Type>
      <Value>ivan@domain.de</Value>
    </Contact>
  </Contacts>
  <HomeAddress>
    <City>Bonn</City>
    <Street>Beethoven str., 100</Street>
  </HomeAddress>
</Person>

Besonderheiten von Kollektionen im XmlSerializer

Standardmäßig serialisiert der XmlSerializer eine Kollektion als Tag "Contacts", innerhalb dessen für jedes Element ein eigener Tag "Contact" erzeugt wird. Der Collection-Typ muss public sein und seine Elemente müssen serialisierbare Typen sein.

Wichtig. Wenn die Kollektion leer ist, enthält das XML trotzdem ein leeres Tag:

<Contacts />

Unterstützte Kollektionen. Der XmlSerializer unterstützt List<T>, Arrays T[], Kollektionen, die ICollection<T> implementieren. Er unterstützt jedoch nicht z.B. Dictionary<TKey, TValue>! Für die Serialisierung von Dictionaries sind zusätzliche Techniken oder Drittanbieterbibliotheken nötig.

6. Unterstützung von Objekten und Kollektionen in Newtonsoft.Json

Newtonsoft.Json ist in Sachen Verschachtelung etwas flexibler. In 99% der Fälle funktioniert alles wie bei System.Text.Json, aber es gibt Vorteile: man kann z.B. private Felder serialisieren (wenn explizit konfiguriert), Dictionaries mit komplexeren Keys, dynamische Typen und sogar zyklische Referenzen (mit speziellen Einstellungen).

Beispiel

using Newtonsoft.Json;

var person = new Person
{
    Name = "Pavel",
    Age = 40,
    Contacts = new List<Contact>
    {
        new Contact { Type = "SMS", Value = "+10000000013" }
    }
};
string json = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(json);

Unterschied. Haben Objekte z.B. private Felder, kann man deren Serialisierung über einen ContractResolver einstellen. Für grundlegende verschachtelte Kollektionen und Objekte funktioniert aber auch hier alles automatisch "out of the box".

7. Typische Fehler, Fallstricke und Feinheiten

Fehler 1: Verschachtelte Objekte sind null.
Wenn bei dem zu serialisierenden Objekt einige Properties nicht gesetzt sind (null), können sie im JSON oder XML fehlen oder als null erscheinen. Beispiel: ist HomeAddress bei Person null, dann im JSON:

"HomeAddress": null

Im XML fehlt der entsprechende Tag standardmäßig — man kann ihn aber per Serializer-Einstellungen hinzufügen ([XmlElement(IsNullable=true)]).

Fehler 2: Serialisierung privater Properties/Felder.
Standardmäßig arbeiten die meisten Serializer (sowohl JSON als auch XML) nur mit public Properties. Willst du private oder protected Felder serialisieren, musst du sie public machen oder den Serializer konfigurieren (z.B. über ContractResolver in Newtonsoft.Json).

Fehler 3: Klassen ohne parameterlosen Konstruktor.
Bei XML-Serialisierung (und oft auch bei JSON) muss eine Klasse einen public parameterlosen Konstruktor haben. Fehlt dieser, bekommst du beim Deserialisieren eine Exception.

Fehler 4: Serialisierung von Dictionaries in XML.
Der XmlSerializer unterstützt nicht direkt die Serialisierung von Dictionary<TKey, TValue>. Das überrascht oft. Lösung: das Dictionary in eine Liste von Paaren wrappen oder andere Serializer benutzen.

Fehler 5: Zyklische Referenzen.
Wenn ein Objekt über eine Referenz ein anderes Objekt enthält, das wiederum zurück auf das erste verweist (z.B. Parent/Child), kann die Serialisierung in eine Endlosschleife oder einen StackOverflow (StackOverflowException) laufen oder eine Exception wegen zyklischer Referenzen werfen. Bei JSON-Serializer kann man das mit Einstellungen umgehen, aber oft ist es besser, die Objektarchitektur zu überdenken.

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