JSON ist die Art, wie die meisten C#-Programme mit Web-APIs sprechen, Einstellungen speichern und Daten austauschen. Modernes .NET liest und schreibt es mit System.Text.Json, das ab .NET Core 3.0 Teil der Runtime ist: Es ist kein NuGet-Paket nötig. Der Haupteinstiegspunkt ist die statische Klasse JsonSerializer, die Objekte in JSON-Strings verwandelt und zurück.
System.Text.Json ist in .NET Core 3.0 und allen späteren Versionen eingebaut (.NET 5 bis .NET 10). Projekte auf .NET Framework 4.6.2 oder neuer oder auf .NET Standard 2.0 können es ebenfalls nutzen, indem sie das NuGet-Paket System.Text.Json installieren. Die Beispiele auf dieser Seite werden als einfacher Code mit der Ausgabe in Kommentaren gezeigt. Füge diese using-Direktiven hinzu, um sie in einem .NET-Projekt auszuführen:
using System.Text.Json;
using System.Text.Json.Serialization;
Serialisieren: Objekt in JSON
JsonSerializer.Serialize schreibt jede öffentliche Property eines Objekts und verwendet die Property-Namen, wie sie sind:
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public bool InStock { get; set; }
}
var lamp = new Product { Name = "Desk lamp", Price = 34.90m, Tags = { "home", "light" }, InStock = true };
string json = JsonSerializer.Serialize(lamp);
Console.WriteLine(json);
// {"Name":"Desk lamp","Price":34.90,"Tags":["home","light"],"InStock":true}
Collections werden zu Arrays, Dictionaries mit String-Schlüsseln zu Objekten ({"apples":3,"pears":5}), null bleibt null, und DateTime wird zu einem String nach ISO 8601 ("2026-03-01T14:30:00"). Zwei Standards erwischen Leute:
- Felder werden übersprungen. Nur Properties werden serialisiert. Eine Klasse mit
public int X;wird als{}serialisiert, außer du setztIncludeFields = truein den Optionen oder machst aus dem Feld eine Property. - Enums sind Zahlen.
OrderStatus.Shippedwird als1geschrieben (der zugrunde liegende Wert des Enums). ErgänzeJsonStringEnumConverter(unten), um"Shipped"zu schreiben.
Deserialisieren: JSON in Objekt
JsonSerializer.Deserialize<T> erzeugt ein T und setzt seine Properties aus dem JSON:
string json = "{\"Name\":\"Desk lamp\",\"Price\":34.90,\"Tags\":[\"home\",\"light\"]}";
Product p = JsonSerializer.Deserialize<Product>(json);
Console.WriteLine($"{p.Name} {p.Price} {p.Tags.Count}"); // Desk lamp 34.90 2
var many = JsonSerializer.Deserialize<List<Product>>("[{\"Name\":\"A\",\"Price\":1},{\"Name\":\"B\",\"Price\":2.5}]");
Console.WriteLine(many.Count); // 2
JSON-Properties ohne passende C#-Property werden ignoriert, und C#-Properties ohne passendes JSON behalten ihre Standardwerte. Keines davon ist standardmäßig ein Fehler.
Die Regel, über die fast jeder stolpert: Der Namensabgleich beachtet Groß- und Kleinschreibung. Die meisten Web-APIs senden camelCase, und camelCase passt nicht auf Properties in PascalCase:
string fromApi = "{\"name\":\"Mug\",\"price\":8.5}";
var a = JsonSerializer.Deserialize<Product>(fromApi);
Console.WriteLine($"[{a.Name}] {a.Price}"); // [] 0: nothing matched, no error
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var b = JsonSerializer.Deserialize<Product>(fromApi, options);
Console.WriteLine($"[{b.Name}] {b.Price}"); // [Mug] 8.5
new JsonSerializerOptions(JsonSerializerDefaults.Web) liefert die Einstellungen, die ASP.NET Core verwendet: Lesen ohne Beachtung der Schreibweise, Schreiben in camelCase und Zahlen, die als Strings in Anführungszeichen stehen, werden akzeptiert.
Die Deserialisierung braucht einen Weg, jeden Wert zu setzen: einen öffentlichen Setter, einen init-Accessor oder einen Konstruktor, dessen Parameternamen zu den Properties passen. Diese letzte Regel ist der Grund, warum Records sofort funktionieren:
public record Point(int X, int Y);
Point pt = JsonSerializer.Deserialize<Point>("{\"X\":1,\"Y\":2}");
Console.WriteLine(pt); // Point { X = 1, Y = 2 }
Optionen: camelCase und eingerückte Ausgabe
JsonSerializerOptions steuert Benennung, Formatierung und mehr. Erzeuge eine Instanz und verwende sie wieder: Der Serializer speichert Metadaten pro Options-Instanz zwischen, für jeden Aufruf neue Optionen zu bauen ist also messbar langsamer.
private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
Console.WriteLine(JsonSerializer.Serialize(lamp, Options));
// {
// "name": "Desk lamp",
// "price": 34.90,
// "tags": [
// "home",
// "light"
// ],
// "inStock": true
// }
Weitere Optionen, die du kennen solltest: DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, um null-Properties wegzulassen, IncludeFields = true, NumberHandling, um als Strings geschriebene Zahlen zu akzeptieren, und ReadCommentHandling = JsonCommentHandling.Skip plus AllowTrailingCommas = true für von Hand bearbeitete Konfigurationsdateien. .NET 8 hat JsonNamingPolicy.SnakeCaseLower für APIs hinzugefügt, die snake_case verwenden.
Attribute: umbenennen, ignorieren, Enums als Strings
Attribute an der Klasse steuern jeweils eine Property und haben Vorrang vor den Optionen:
public enum OrderStatus { Pending, Shipped }
public class Order
{
[JsonPropertyName("order_id")]
public int Id { get; set; }
public string Customer { get; set; }
[JsonIgnore]
public string InternalNote { get; set; } // never written or read
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Coupon { get; set; } // left out when null
[JsonConverter(typeof(JsonStringEnumConverter))]
public OrderStatus Status { get; set; }
public DateTime PlacedAt { get; set; }
}
var order = new Order
{
Id = 1042, Customer = "Ana", InternalNote = "vip",
Status = OrderStatus.Shipped, PlacedAt = new DateTime(2026, 3, 1, 14, 30, 0),
};
Console.WriteLine(JsonSerializer.Serialize(order));
// {"order_id":1042,"Customer":"Ana","Status":"Shipped","PlacedAt":"2026-03-01T14:30:00"}
Um jedes Enum als String zu schreiben, statt jede Property zu markieren, füge den Konverter zu den Optionen hinzu: options.Converters.Add(new JsonStringEnumConverter());.
JSON ohne Klasse lesen: JsonDocument und JsonNode
Wenn du aus einer großen Antwort nur ein paar Werte brauchst oder ihre Form variiert, verzichte auf die Klasse. JsonDocument parst in einen schreibgeschützten Baum aus JsonElement-Werten:
string weather = "{\"city\":\"Lisbon\",\"current\":{\"temp\":21.5,\"conditions\":[\"sunny\",\"windy\"]},\"alerts\":null}";
using (JsonDocument doc = JsonDocument.Parse(weather))
{
JsonElement root = doc.RootElement;
Console.WriteLine(root.GetProperty("city").GetString()); // Lisbon
Console.WriteLine(root.GetProperty("current").GetProperty("temp").GetDecimal()); // 21.5
foreach (JsonElement c in root.GetProperty("current").GetProperty("conditions").EnumerateArray())
Console.WriteLine(c.GetString()); // sunny, windy
Console.WriteLine(root.TryGetProperty("humidity", out _)); // False
Console.WriteLine(root.GetProperty("alerts").ValueKind); // Null
}
GetProperty wirft bei einem fehlenden Namen KeyNotFoundException, nimm für optionale Felder also TryGetProperty. JsonDocument leiht sich Speicher aus einem Pool, deshalb wird es mit using freigegeben.
Um JSON zu ändern, nimm JsonNode aus System.Text.Json.Nodes (ab .NET 6), das einen veränderlichen Baum mit Indexern liefert:
JsonNode node = JsonNode.Parse(weather);
Console.WriteLine((string)node["city"]); // Lisbon
node["current"]["temp"] = 23;
node["updated"] = true;
Console.WriteLine(node.ToJsonString());
// {"city":"Lisbon","current":{"temp":23,"conditions":["sunny","windy"]},"alerts":null,"updated":true}
Dateien und Streams
JSON auf der Festplatte ist ein String in einer Datei, die Dateimethoden lassen sich also direkt mit dem Serializer kombinieren:
File.WriteAllText("settings.json", JsonSerializer.Serialize(settings, Options));
var loaded = JsonSerializer.Deserialize<Settings>(File.ReadAllText("settings.json"), Options);
// Same options both ways: camelCase names written with Options would not match on a default read.
Für große Dateien und HTTP-Bodies vermeiden die async-Überladungen für Streams, den ganzen String im Speicher aufzubauen:
await using FileStream stream = File.OpenRead("orders.json");
List<Order> orders = await JsonSerializer.DeserializeAsync<List<Order>>(stream);
In ASP.NET Core und mit HttpClient rufst du den Serializer selten selbst auf: Controller binden JSON-Bodies automatisch, und httpClient.GetFromJsonAsync<Order>(url) (in System.Net.Http.Json) erledigt Anfrage und Deserialisierung in einem Aufruf.
Fehler
Ungültiges JSON oder ein Wert, der sich nicht in den Typ der Property umwandeln lässt, wirft JsonException. Ihre Meldung nennt den JSON-Pfad und die Position, was meist reicht, um das Problem zu finden:
try
{
JsonSerializer.Deserialize<Product>("{\"Name\": \"Lamp\", \"Price\": \"cheap\"}");
}
catch (JsonException e)
{
Console.WriteLine(e.Message);
// The JSON value could not be converted to System.Decimal. Path: $.Price | LineNumber: 0 | BytePositionInLine: 33.
}
Deserialize gibt null zurück (keine Exception), wenn der JSON-Text das Literal null ist, prüfe das Ergebnis also, wenn die Eingabe von außen kommt.
Escapete Zeichen in der Ausgabe
Standardmäßig escaped der Serializer Nicht-ASCII-Zeichen und Zeichen, die in HTML unsicher sind:
Console.WriteLine(JsonSerializer.Serialize(new { city = "São Paulo", note = "5 > 3" }));
// {"city":"S\u00E3o Paulo","note":"5 \u003E 3"}
Das ist gültiges JSON und wird als ursprünglicher Text zurückgelesen; es wird escaped, damit die Ausgabe sicher in eine HTML-Seite eingefügt werden kann. Für menschenlesbare Dateien setze Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping (aus System.Text.Encodings.Web) in den Optionen. Das „unsafe“ bezieht sich nur darauf, das Ergebnis in HTML einzubetten.
System.Text.Json gegenüber Newtonsoft.Json
Newtonsoft.Json (Json.NET, die Klasse JsonConvert) war ein Jahrzehnt lang der Standard und ist immer noch überall. Die wichtigsten Unterschiede:
| System.Text.Json | Newtonsoft.Json | |
|---|---|---|
| Verfügbarkeit | eingebaut ab .NET Core 3.0; NuGet-Paket für .NET Framework 4.6.2+ | NuGet-Paket für .NET Framework und .NET |
| Serialisieren / deserialisieren | JsonSerializer.Serialize(obj) / Deserialize<T>(json) | JsonConvert.SerializeObject(obj) / DeserializeObject<T>(json) |
| Namensabgleich | beachtet standardmäßig Schreibweise | ignoriert Schreibweise |
| Untypisiertes Lesen | JsonDocument, JsonNode | JObject, JToken, mit JSONPath-Abfragen |
| Property umbenennen | [JsonPropertyName("x")] | [JsonProperty("x")] |
| Toleranz | streng: keine Kommentare, abschließenden Kommas oder Zahlen in Anführungszeichen, außer aktiviert | standardmäßig tolerant |
| Performance | schneller, weniger Allokationen, Source Generation für Trimming und AOT | langsamer, reflectionbasiert |
Die Attribute haben andere Namen und Namespaces, eine Codebasis umzustellen ist also Suchen und Ersetzen plus Tests für das strengere Parsen. Beginne bei neuem .NET-Code mit System.Text.Json.
Source Generation (.NET 6+)
JsonSerializer untersucht deine Typen normalerweise zur Laufzeit per Reflection. Für Apps, die getrimmt oder vorab kompiliert werden (Native AOT, Blazor WebAssembly), schreibt ein Source Generator diesen Code stattdessen zur Kompilierzeit:
[JsonSerializable(typeof(Product))]
internal partial class AppJsonContext : JsonSerializerContext { }
string json = JsonSerializer.Serialize(lamp, AppJsonContext.Default.Product);
Häufige Fehler
- JSON in camelCase in Properties in PascalCase mit Standardoptionen. Jede Property bleibt stillschweigend leer. Nimm
PropertyNameCaseInsensitiveoderJsonSerializerDefaults.Web. - Öffentliche Felder statt Properties. Sie werden nicht serialisiert, außer
IncludeFieldsist gesetzt. - Properties ohne Setter. Eine schreibgeschützte Property ohne passenden Konstruktorparameter wird beim Deserialisieren nicht gefüllt.
- Ein neues
JsonSerializerOptionspro Aufruf. Verwende eine statische Instanz wieder. - Geld als Gleitkommazahl. Ein
doubleaus0.1 + 0.2wird als0.30000000000000004serialisiert. Nimmdecimalfür Beträge.
Häufig gestellte Fragen
Wie wandle ich in C# ein Objekt in JSON um?
Rufe JsonSerializer.Serialize(obj) aus System.Text.Json auf, das ab .NET Core 3.0 eingebaut ist, ohne ein Paket zu installieren. Es schreibt jede öffentliche Property: {"Name":"Desk lamp","Price":34.90}. Übergib new JsonSerializerOptions { WriteIndented = true } für lesbare Ausgabe und PropertyNamingPolicy = JsonNamingPolicy.CamelCase für Namen in camelCase.
Wie wandle ich in C# JSON in ein Objekt um?
var product = JsonSerializer.Deserialize<Product>(json); erzeugt ein Product und füllt seine öffentlichen, setzbaren Properties aus den passenden JSON-Namen. Der Abgleich beachtet standardmäßig Groß- und Kleinschreibung, JSON in camelCase lässt Properties in PascalCase also leer, außer du übergibst PropertyNameCaseInsensitive = true oder new JsonSerializerOptions(JsonSerializerDefaults.Web). Fehlerhaftes JSON oder ein Wert vom falschen Typ wirft JsonException.
Wie lese ich in C# JSON, ohne eine Klasse zu erstellen?
Nimm JsonDocument.Parse(json) und durchlaufe RootElement mit GetProperty("name"), GetString(), GetInt32() und EnumerateArray(); es ist schreibgeschützt, schnell und muss freigegeben werden. Für JSON, das du ändern willst, gibt dir JsonNode.Parse(json) (.NET 6+) einen veränderlichen Baum: node["city"], Zuweisungen und ToJsonString().
Soll ich System.Text.Json oder Newtonsoft.Json verwenden?
Für neuen Code ab .NET Core 3.0 System.Text.Json: Es ist eingebaut, schneller, allokiert weniger, und ASP.NET Core verwendet es standardmäßig. Newtonsoft.Json (Json.NET) ist weiterhin verbreitet in Projekten mit .NET Framework (wo System.Text.Json nur als NuGet-Paket verfügbar ist), in Code, der von seinen zusätzlichen Features abhängt (JSONPath-Abfragen, sehr tolerantes Parsen, TypeNameHandling), und in großen Codebasen, die bereits darauf aufbauen.
Warum escaped System.Text.Json Zeichen wie é und <?
Der Standard-Encoder escaped Nicht-ASCII-Zeichen und für HTML heikle Zeichen (<, >, &, ') als \uXXXX, damit die Ausgabe sicher in HTML eingebettet werden kann. Es ist trotzdem gültiges JSON und wird zum selben Text deserialisiert. Für lesbare Ausgabe setze Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping in den Optionen, aber nur, wenn das JSON nicht in HTML geschrieben wird.