Zum Inhalt springen
  • Dunkel
  • Hell
  • System

JSON verarbeiten mit Newtonsoft.Json

Sobald du in C# mit API-Antworten arbeitest oder ein zusammengesetztes Objekt in einem Global ablegen willst, kommst du an JSON nicht vorbei. Streamer.bot bringt dafür die Bibliothek Newtonsoft.Json schon mit, sie ist in jeder C#-Code-Action mit Execute standardmäßig referenziert. Du musst nichts installieren und nichts im References-Tab nachtragen, JsonConvert und JObject stehen direkt zur Verfügung. Zwei Wege gibt es: eine eigene Klasse bei festem Schema oder JObject für den lockeren Zugriff, wenn du nur ein Feld brauchst.

Doku: docs.streamer.bot · C# Methoden

Serialisieren heißt: aus einem C#-Objekt einen JSON-String machen. Das brauchst du, wenn du etwas Komplexeres als eine einfache Zahl speichern oder verschicken willst. JsonConvert.SerializeObject nimmt ein beliebiges Objekt und gibt den passenden String zurück.

public class CPHInline {
public bool Execute() {
var spende = new {
user = "azazeli",
betrag = 5,
nachricht = "Viel Erfolg!"
};
// Objekt -> JSON-String
string json = JsonConvert.SerializeObject(spende);
// ergibt: {"user":"azazeli","betrag":5,"nachricht":"Viel Erfolg!"}
CPH.LogInfo(json);
return true;
}
}

Hier dient ein anonymes Objekt als Quelle. Genauso gut funktioniert eine eigene Klasse, ein Dictionary<string, object> oder eine List<T>. Newtonsoft kümmert sich um die korrekte JSON-Struktur, inklusive verschachtelter Objekte und Arrays.

Der umgekehrte Weg: aus einem JSON-String wieder ein C#-Objekt machen. Dafür legst du eine Klasse an, deren Eigenschaften zu den JSON-Feldern passen, und gibst sie als generischen Typ-Parameter an JsonConvert.DeserializeObject<T> mit.

public class CPHInline {
public class Spende {
public string User { get; set; }
public int Betrag { get; set; }
public string Nachricht { get; set; }
}
public bool Execute() {
string json = "{\"user\":\"azazeli\",\"betrag\":5,\"nachricht\":\"Viel Erfolg!\"}";
// JSON-String -> typisiertes Objekt
Spende s = JsonConvert.DeserializeObject<Spende>(json);
CPH.SendMessage($"💜 {s.User} hat {s.Betrag} gespendet: {s.Nachricht}");
return true;
}
}

Die Property-Namen werden ohne Rücksicht auf Groß- und Kleinschreibung zugeordnet, User matcht also user. Eine eigene Klasse lohnt sich, sobald das Schema feststeht und du mehrere Felder typsicher weiterverarbeitest. Du bekommst Autovervollständigung auf s.User und s.Betrag und der Compiler warnt bei Tippfehlern.

Wenn du nur ein einziges Feld aus einer fremden Antwort herausziehen willst, ist eine eigene Klasse mit allen Feldern Overkill. JObject.Parse parst den String in einen flexiblen Baum, auf den du per Index zugreifst. Ein typischer Fall ist eine Wetter-API, die {"main":{"temp":5}} zurückgibt und aus der du nur die Temperatur brauchst.

public class CPHInline {
public bool Execute() {
string json = "{\"main\":{\"temp\":5},\"name\":\"Berlin\"}";
JObject jobj = JObject.Parse(json);
// verschachtelt zugreifen: main -> temp
string temp = jobj["main"]["temp"].ToString();
string ort = jobj["name"].ToString();
CPH.SendMessage($"🌡️ In {ort} sind es {temp} Grad.");
return true;
}
}

Mit jobj["main"]["temp"] hangelst du dich durch die Verschachtelung. .ToString() macht aus dem JSON-Token einen lesbaren String. Brauchst du einen typisierten Wert, geht das direkt mit jobj["main"]["temp"].Value<int>(). Ein Array iterierst du mit foreach (var eintrag in jobj["liste"]).

Beide Wege parsen denselben JSON-String, die Wahl hängt am Anwendungsfall.

SituationEmpfehlung
Festes Schema, viele Felder, mehrfach genutzteigene Klasse mit DeserializeObject<T>
Nur ein, zwei Felder aus einer fremden AntwortJObject.Parse und per Index zugreifen
Schema ändert sich oft oder ist nicht dokumentiertJObject, weil du keine Klasse pflegen musst
Daten werden typsicher weiterverarbeiteteigene Klasse, du bekommst Autovervollständigung

Faustregel: feste, wiederkehrende Struktur ruft nach einer Klasse. Mal eben ein Feld aus einer API-Antwort rausziehen ruft nach JObject.

CPH.SetGlobalVar nimmt einen object-Wert, aber bei verschachtelten Objekten oder Listen fährst du sicherer, wenn du selbst zu einem String serialisierst und ihn ablegst. Beim Lesen deserialisierst du zurück. So überlebt eine komplette Liste den Restart, ohne dass du dich auf das interne Serialisieren verlassen musst.

public class CPHInline {
public class Quote {
public string Text { get; set; }
public string Author { get; set; }
}
public bool Execute() {
// bestehende Liste laden, null-sicher
string raw = CPH.GetGlobalVar<string>("quotes", true);
var quotes = raw == null
? new List<Quote>()
: JsonConvert.DeserializeObject<List<Quote>>(raw);
// neues Zitat anhängen
quotes.Add(new Quote { Text = "GG WP", Author = "azazeli" });
// wieder als JSON-String persistieren
CPH.SetGlobalVar("quotes", JsonConvert.SerializeObject(quotes), true);
CPH.SendMessage($"📜 Zitate gespeichert: {quotes.Count}");
return true;
}
}

Beim ersten Aufruf liefert GetGlobalVar<string> ein null, dann startest du mit einer leeren Liste. Danach steht im Global ein JSON-Array, das du jedes Mal lädst, ergänzt und zurückschreibst. Mehr zum dauerhaften Ablegen größerer Datenmengen steht unter Daten-Persistenz.

  • Generics-Tippfehler bei DeserializeObject<T>: Der Typ-Parameter muss zur JSON-Struktur passen. Ein JSON-Array deserialisierst du nach List<Quote>, ein einzelnes Objekt nach Quote. Verwechselst du das, wirft Newtonsoft eine JsonSerializationException.
  • Fehlende Felder: Fehlt ein Feld im JSON, ist die entsprechende Property im Objekt null (bei Referenztypen) oder der Default 0 (bei int). Vor dem Weiterverarbeiten prüfen, sonst gibt es eine NullReferenceException. Bei JObject liefert ein nicht existenter Schlüssel wie jobj["fehlt"] ebenfalls null, und .ToString() darauf kracht.
  • Falscher Typ beim Auslesen: jobj["main"]["temp"].Value<int>() auf einem Feld, das in Wahrheit ein String wie "5°C" ist, wirft einen Konvertierungsfehler. Im Zweifel .ToString() nehmen und selbst parsen.
  • Verschachtelte Arrays: Bei {"items":[{"id":1}]} ist jobj["items"] ein Array, kein Objekt. Du iterierst mit foreach (var i in jobj["items"]) und greifst dann auf i["id"] zu. Direkt jobj["items"]["id"] schlägt fehl, weil ein Array keinen Feldnamen kennt.
  • Manuelles String-Bauen statt SerializeObject: JSON per $"{{\"key\":\"{wert}\"}}" zusammenstückeln bricht, sobald wert ein Anführungszeichen oder einen Umlaut enthält. Immer JsonConvert.SerializeObject nutzen, das escaped korrekt.