Menu

C# JSON: serialisieren und deserialisieren mit System.Text.Json

Wie du in C# mit System.Text.Json mit JSON arbeitest: JsonSerializer.Serialize und Deserialize, camelCase und eingerückte Ausgabe, Attribute wie JsonPropertyName und JsonIgnore, Enums als Strings, JSON ohne Klassen lesen mit JsonDocument und JsonNode, Fehler und der Vergleich mit Newtonsoft.Json.

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 setzt IncludeFields = true in den Optionen oder machst aus dem Feld eine Property.
  • Enums sind Zahlen. OrderStatus.Shipped wird als 1 geschrieben (der zugrunde liegende Wert des Enums). Ergänze JsonStringEnumConverter (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.JsonNewtonsoft.Json
Verfügbarkeiteingebaut ab .NET Core 3.0; NuGet-Paket für .NET Framework 4.6.2+NuGet-Paket für .NET Framework und .NET
Serialisieren / deserialisierenJsonSerializer.Serialize(obj) / Deserialize<T>(json)JsonConvert.SerializeObject(obj) / DeserializeObject<T>(json)
Namensabgleichbeachtet standardmäßig Schreibweiseignoriert Schreibweise
Untypisiertes LesenJsonDocument, JsonNodeJObject, JToken, mit JSONPath-Abfragen
Property umbenennen[JsonPropertyName("x")][JsonProperty("x")]
Toleranzstreng: keine Kommentare, abschließenden Kommas oder Zahlen in Anführungszeichen, außer aktiviertstandardmäßig tolerant
Performanceschneller, weniger Allokationen, Source Generation für Trimming und AOTlangsamer, 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 PropertyNameCaseInsensitive oder JsonSerializerDefaults.Web.
  • Öffentliche Felder statt Properties. Sie werden nicht serialisiert, außer IncludeFields ist gesetzt.
  • Properties ohne Setter. Eine schreibgeschützte Property ohne passenden Konstruktorparameter wird beim Deserialisieren nicht gefüllt.
  • Ein neues JsonSerializerOptions pro Aufruf. Verwende eine statische Instanz wieder.
  • Geld als Gleitkommazahl. Ein double aus 0.1 + 0.2 wird als 0.30000000000000004 serialisiert. Nimm decimal fü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.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S