CodeGym /Kurse /C# SELF /Dynamische Strukturen: JOb...

Dynamische Strukturen: JObject, JArray

C# SELF
Level 47 , Lektion 3
Verfügbar

1. Einführung

In den vorherigen Vorlesungen haben wir immer mit strikt typisierten Strukturen gearbeitet. Zum Beispiel haben wir eine Klasse Person, die wir in JSON serialisieren und zurück:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Aber manchmal kennst du die Datenstruktur nicht im Voraus. Zum Beispiel:

  • Du schreibst einen Parser für einen externen Service, bei dem die Antwortstruktur nicht fest ist.
  • Du musst nur einen Teil der Informationen extrahieren, nicht unbedingt eine ganze Klasse füllen.
  • Du musst JSON "on the fly" editieren oder generieren, basierend auf dynamischen Bedingungen.

Hier kommen die "dynamischen Strukturen" ins Spiel — Objekte, die den JSON-Baum als Schlüssel-Wert-Paare halten, ohne dass eine vorher beschriebene C#-Klasse nötig ist.

Warum meistens Newtonsoft.Json benutzt wird

In .NET gibt es zwei Hauptspieler im Bereich JSON:

  • System.Text.Json — die eingebaute Bibliothek von Microsoft (entwickelt seit .NET Core 3.0).
  • Newtonsoft.Json (Json.NET) — eine populäre Bibliothek, die der C#-Welt Klassen wie JObject und JArray gebracht hat.

Zum Zeitpunkt dieser Vorlesung bietet System.Text.Json noch nicht das volle Pendant zu JObject/JArray mit dem gleichen Komfort. Deshalb wählt man oft Newtonsoft.Json, wenn man häufig komplexe/ungewisse JSON-Strukturen parsen oder modifizieren muss.

Grundtypen: JObject, JArray, JValue und die Familie der JToken

  • JToken — Basistyp für alle JSON-Knoten.
  • JObject — JSON-Objekt { ... }, eine Menge von "key-value"-Paaren.
  • JArray — Array [ ... ].
  • JValue — ein einzelner Wert (42, "text", true, null).

Die Idee ist einfach: wir parsen JSON — erhalten einen "Baum" aus diesen Tokens und können ihn durchlaufen, suchen, ändern, löschen und Elemente hinzufügen.

2. Lesen von unbekanntem JSON

Angenommen, wir haben dieses JSON erhalten und wollen nicht oder können nicht vorher eine Klasse dafür schreiben:

{
  "status": "ok",
  "amount": 150.5,
  "items": [
    {
      "name": "book",
      "qty": 1
    },
    {
      "name": "pen",
      "qty": 3
    }
  ]
}

Mit Newtonsoft.Json können wir es in einen Baum verwandeln und zur Laufzeit untersuchen.

Beispiel: JSON in ein JObject lesen

using Newtonsoft.Json.Linq;

string json = @"{
  ""status"": ""ok"",
  ""amount"": 150.5,
  ""items"": [
    { ""name"": ""book"", ""qty"": 1 },
    { ""name"": ""pen"", ""qty"": 3 }
  ]
}";

// Wir parsen den String und bekommen den Baum
JObject root = JObject.Parse(json);

// Eigenschaften wie in einem Dictionary holen
string status = (string)root["status"]; // "ok"
double amount = (double)root["amount"]; // 150.5

// items ist ein Array, also JArray
JArray items = (JArray)root["items"];

// Array durchlaufen
foreach (JObject item in items)
{
    string name = (string)item["name"];
    int qty = (int)item["qty"];
    Console.WriteLine($"Produkt: {name}, Menge: {qty}");
}

Sehr praktisch: keine Klassen-Definition nötig — du kannst schnell die benötigten Teile herausziehen.

3. Indexer und dynamischer Zugriff

Indexer:

  • Für ein Objekt: root["status"], root["items"]
  • Für ein Array: items[0], items[1]

Um den Wert direkt im gewünschten Typ zu bekommen, benutze Casts ((string), (int), (bool), (double)) — die Bibliothek konvertiert automatisch.

Wenn die Daten fehlen können, sei vorsichtig: Zugriff auf einen nicht vorhandenen Schlüssel gibt null zurück, und direkter Cast wird eine Exception werfen. Besser Methoden mit Prüfung verwenden:

if (root.TryGetValue("amount", out var token))
{
    double amount = token.Value<double>();
    // Diese Variante ist oft komfortabler: Value<T>() konvertiert direkt
}

Verschachtelte Objekte und Arrays werden genauso einfach gelesen:

// Zweites Item bekommen
JObject secondItem = (JObject)root["items"][1];
string itemName = (string)secondItem["name"]; // "pen"

Man kann auch dynamisch arbeiten:

dynamic droot = root;
Console.WriteLine(droot.status); // "ok"

Aber denk dran: mit dynamic prüft der Compiler nicht den Zugriff auf Felder — Fehler treten erst zur Laufzeit auf.

4. JSON-Baum zur Laufzeit modifizieren

Elemente hinzufügen

root["currency"] = "RUB";        // Neues Property hinzugefügt
items.Add(new JObject
{
    ["name"] = "eraser",
    ["qty"] = 2
});

Ändern und Löschen

root["status"] = "done";         // Wert geändert
items[0]["qty"] = 5;             // Menge des ersten Produkts erhöht
items.RemoveAt(1);               // Zweites Produkt entfernt

Als String speichern

string modifiedJson = root.ToString();
// Oder root.ToString(Formatting.Indented) für schön formatierten Output

5. JSON-Struktur von Grund auf erstellen

var person = new JObject
{
    ["name"] = "Alice",
    ["age"] = 22,
    ["languages"] = new JArray { "C#", "Python" },
    ["isStudent"] = true
};

Console.WriteLine(person.ToString(Newtonsoft.Json.Formatting.Indented));
{
  "name": "Alice",
  "age": 22,
  "languages": [
    "C#",
    "Python"
  ],
  "isStudent": true
}

Nützlich, wenn du einer externen API nur Teile der Daten zurückgeben oder JSON bedingt zusammenbauen musst.

6. Wie man ein eigenes Objekt aus JObject baut

Manchmal musst du flexibles JSON in ein striktes C#-Objekt umwandeln. Optionen:

  • Normale Deserialisierung: JsonConvert.DeserializeObject<MyClass>(...).
  • Manuelles Aufbauen des Objekts, indem du die benötigten Felder aus dem JObject ziehst.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Angenommen, dieses JSON kam an:
string incoming = @"{ ""name"":""Bob"", ""age"":30, ""extraField"":true }";

JObject j = JObject.Parse(incoming);

// Objekt manuell zusammenbauen — nur die benötigten Felder
var person = new Person
{
    Name = (string)j["name"],
    Age = (int)j["age"],
    // extraField brauchen wir nicht — perfekt!
};

7. Beispiele für Fehler und Feinheiten: typische Fallen

Mit dynamischen JSON-Strukturen zu arbeiten ist flexibel, aber es gibt "untewssbare Stellen":

  • Zugriff auf einen nicht existierenden Schlüssel/Eintrag gibt null; ein direkter Cast zu einem Value-Type wirft eine Exception.
  • Unterschiedliche Typen als erwartet (du erwartest ein Objekt, bekommst aber einen Wert) — Cast-Fehler.
  • Zum Durchlaufen der Felder eines Objekts benutze Properties():
foreach (var prop in root.Properties())
{
    Console.WriteLine($"Feld: {prop.Name}, Wert: {prop.Value}");
}
  • Mit Newtonsoft.Json lässt sich gut mit LINQ-ähnlichen Queries arbeiten (Filtern, Suchen):
var expensiveItems = items.Where(obj => (int)obj["qty"] > 2);
foreach (var item in expensiveItems)
    Console.WriteLine(item);

8. Typische Fehler bei der Arbeit mit JObject/JArray

Fehler Nr. 1: fehlendes erwartetes Feld oder Typ-Mismatch. Sehr oft geht man davon aus, dass ein Feld vorhanden ist, aber es fehlt oder hat einen anderen Typ. Wenn du ein Objekt erwartest und eine Zahl kommt, führt das beim Cast zu einer Exception. Prüfe Existenz und Typ bevor du es benutzt.

Fehler Nr. 2: Zugriff auf Felder verschachtelter Strukturen ohne null-Checks. Wenn JSON verschachtelte Objekte hat und Keys unterschiedlich oder fehlen, kann der Zugriff auf ein nicht vorhandenes Feld abstürzen. Prüfe auf null, bevor du verschachtelte Knoten liest.

Fehler Nr. 3: Cast zu einem Value-Type wenn der Wert null ist. Wenn der Key existiert, aber sein Wert null ist, wird ein Ausdruck wie (int)j["age"] eine Exception werfen. Nutze Value<T>() — das gibt den Default-Wert zurück (für int das 0, für string — null).

Fehler Nr. 4: zu viel Dynamik statt klarer Modelle. Zu komplexe Strukturen sind oft besser und sicherer als C#-Klassen beschrieben: das reduziert Fehler und erhöht die Lesbarkeit. Verwende Dynamik dort, wo die Struktur wirklich unbekannt oder stark variabel ist.

Jetzt weißt du, wie man mit JObject und JArray JSON-Strukturen schnell und sicher parst, ändert und erstellt, sogar ohne vorher definierte Modelle.

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