XML a JSON v C# a .NET

V .NET se XML nejčastěji čte přes LINQ to XML (XDocument), obří soubory proudově přes XmlReader a pro mapování na třídy slouží XmlSerializer. Kapitola ukazuje každý z přístupů na stejném dokumentu, včetně jmenných prostorů, XPath a validace proti XSD.

Autor: Aktualizováno

LINQ to XML: čtení a dotazy

C#
using System.Xml.Linq;

var doc = XDocument.Load("objednavka.xml");         // nebo XDocument.Parse(text)
var root = doc.Root!;

string cislo = (string)root.Attribute("cislo")!;     // "2026-117"
string zakaznik = (string)root.Element("zakaznik")!;

// Dotaz LINQ: položky s více kusy, seřazené podle ceny
var drahe = root.Elements("polozka")
    .Where(p => (int)p.Attribute("mnozstvi")! > 1)
    .OrderByDescending(p => (decimal)p.Attribute("cena")!)
    .Select(p => new { Kod = (string)p.Attribute("kod")!, Nazev = p.Value });

decimal celkem = root.Elements("polozka")
    .Sum(p => (int)p.Attribute("mnozstvi")! * (decimal)p.Attribute("cena")!);

Elements() vrací přímé potomky, Descendants() všechny potomky v libovolné hloubce. Hodnoty čti explicitním přetypováním, ne přes decimal.Parse(x.Value):

C#
// Explicitní přetypování XElement/XAttribute převádí podle pravidel XML Schema,
// tedy nezávisle na jazyku systému: "149.90" je vždy 149,90 (tečka jako oddělovač).
decimal cena = (decimal)polozka.Attribute("cena")!;

// Přetypování na nullable typ vrátí null, když atribut chybí — bez výjimky
int? sleva = (int?)polozka.Attribute("sleva");

XDocument, XmlReader, nebo XmlDocument?

Ukázky pracují s touto objednávkou:

objednavka.xml
<?xml version="1.0" encoding="UTF-8"?>
<objednavka cislo="2026-117">
  <zakaznik>Jana Nováková</zakaznik>
  <polozka kod="HR-01" mnozstvi="2" cena="149.90">Hrnek</polozka>
  <polozka kod="TA-07" mnozstvi="1" cena="219.00">Talíř</polozka>
</objednavka>
XDocument (LINQ to XML)XmlReaderXmlDocument
Styl prácedotazy LINQ nad stromemkurzor vpřed, uzel po uzluklasický DOM (starší API)
Paměťcelý dokumentjen aktuální uzelcelý dokument, víc než XDocument
Úpravy a tvorbapohodlné, funkční konstrukcejen čtení (zápis XmlWriter)ano
XPathpřes System.Xml.XPathneSelectNodes, SelectSingleNode
Kdy použítvětšina běžné prácesoubory ve stovkách MBstarší kód, který ho už používá

Vytvoření XML

C#
XNamespace f = "http://example.cz/faktura";
var doc = new XDocument(
    new XDeclaration("1.0", "utf-8", null),
    new XElement(f + "faktura",
        new XAttribute(XNamespace.Xmlns + "f", f),
        new XElement(f + "cislo", "FV2026-042"),
        new XElement(f + "odberatel", "Petr & syn"),        // & se zapíše jako &amp;
        polozky.Select(p => new XElement(f + "polozka",
            new XAttribute("kod", p.Kod), p.Nazev))));

doc.Save("faktura.xml");

Serializace XML

Při převodu XML stromu na text (serializaci) knihovna zapíše zvláštní znaky podle pravidel XML, například ampersand jako &amp;. Zápis zpět nemusí zachovat původní prefixy, odsazení, CDATA nebo pořadí atributů; testuj obsah a požadované kódování.

string text = doc.ToString(SaveOptions.DisableFormatting);

DOM

XDocument je LINQ to XML, XmlDocument je klasický DOM. Obě reprezentace načítají strom do paměti. XName porovnává URI prostoru i lokální název; XmlDocument nabízí uzly a XPath přes XmlNamespaceManager.

Velké soubory: XmlReader

C#
using System.Xml;
using System.Xml.Linq;

var settings = new XmlReaderSettings { IgnoreWhitespace = true, DtdProcessing = DtdProcessing.Prohibit };
using var reader = XmlReader.Create("feed.xml", settings);

reader.MoveToContent();
while (reader.ReadToFollowing("SHOPITEM"))
{
    // Jeden záznam jako XElement — v paměti je vždy jen on
    var item = (XElement)XNode.ReadFrom(reader);
    Uloz((string)item.Element("PRODUCTNAME")!, (decimal)item.Element("PRICE_VAT")!);
}

Kombinace ReadToFollowing a XNode.ReadFrom spojuje nízkou spotřebu paměti proudového čtení s pohodlím LINQ pro každý jednotlivý záznam.

XPath

C#
using System.Xml.XPath;

var polozky = doc.XPathSelectElements("//polozka[@mnozstvi > 1]");
double pocet = (double)doc.XPathEvaluate("count(//polozka)");

// Se jmenným prostorem
var nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("fa", "http://example.cz/faktura");
var cislo = doc.XPathSelectElement("//fa:cislo", nsm);

.NET implementuje XPath 1.0. Na složitější podmínky je obvykle čitelnější LINQ; XPath se hodí, když výraz přichází z konfigurace nebo ho sdílíš s jiným jazykem.

Jmenné prostory

C#
XNamespace f = "http://example.cz/faktura";
var doc = XDocument.Parse(
    """<f:faktura xmlns:f="http://example.cz/faktura"><f:cislo>FV2026-042</f:cislo></f:faktura>""");

string cislo = (string)doc.Root!.Element(f + "cislo")!;   // "FV2026-042"
// doc.Root.Element("cislo") by vrátilo null — jméno bez jmenného prostoru je jiné jméno

Jméno prvku v LINQ to XML je dvojice jmenný prostor + lokální jméno (XName). Prefix z dokumentu nehraje roli — záleží jen na URI.

Validace proti XSD

C#
using System.Xml.Schema;

var schemas = new XmlSchemaSet();
schemas.Add(null, "objednavka.xsd");

var doc = XDocument.Load("objednavka.xml", LoadOptions.SetLineInfo);
doc.Validate(schemas, (sender, e) =>
{
    var line = (sender as IXmlLineInfo)?.LineNumber;
    Console.WriteLine($"{e.Severity} na řádku {line}: {e.Message}");
});

Bezpečnost: XmlReader a XDocument jsou v moderním .NET ve výchozím nastavení chráněné proti XXE. U XmlDocument ve starším kódu nastav XmlResolver = null a u cizích dat DtdProcessing = DtdProcessing.Prohibit.

Bezpečné nastavení parseru

Vytvoř XmlReader s DtdProcessing.Prohibit, XmlResolver = null a limitem MaxCharactersInDocument. Takový reader předávej také do XDocument.Load a serializátoru. Nastavení se musí uplatnit na skutečně používané cestě čtení; příklad a limity viz XXE.

Mapování na třídy: XmlSerializer

C#
using System.Xml.Serialization;

[XmlRoot("objednavka")]
public class Objednavka
{
    [XmlAttribute("cislo")] public string Cislo { get; set; } = "";
    [XmlElement("zakaznik")] public string Zakaznik { get; set; } = "";
    [XmlElement("polozka")] public List<Polozka> Polozky { get; set; } = new();
}

public class Polozka
{
    [XmlAttribute("kod")] public string Kod { get; set; } = "";
    [XmlAttribute("mnozstvi")] public int Mnozstvi { get; set; }
    [XmlText] public string Nazev { get; set; } = "";
}

var serializer = new XmlSerializer(typeof(Objednavka));
using var stream = File.OpenRead("objednavka.xml");
var objednavka = (Objednavka)serializer.Deserialize(stream)!;

Třídy pro složitá schémata nemusíš psát ručně: nástroj xsd.exe (nebo dotnet-xscgen) je vygeneruje z XSD.

Načtení JSON

JSON parser vrací datovou strukturu, ne XML strom. Chyby syntaxe zpracuj jako chyby vstupu; data ověř také strukturálně.

using System.Text.Json;
using var data = JsonDocument.Parse("{\"jmeno\":\"Jana\",\"vek\":20}");
Console.WriteLine(data.RootElement.GetProperty("jmeno").GetString());

Vytvoření JSON

Vytvoř hodnotu v paměti a nech její textový zápis na knihovně.

var data = new { jmeno = "Jana", vek = 20, aktivni = true };

Serializace JSON

Neskládej text ručně. Ověř přesnost čísel, reprezentaci dat a chování nečíselných hodnot.

string text = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });

JSON Schema

Standardní knihovna neposkytuje plnou validaci JSON Schema. Vyber knihovnu s podporou požadované verze pravidel JSON Schema z oficiální dokumentace či katalogu. .NET může použít JsonSchema.Net. Základy a rozdíly verzí vysvětluje kurz JSON Schema.

Dokumentace API

Příklady JSON vycházejí z oficiální dokumentace jazyka. Překlady typů, maximální hloubka a chování při chybě jsou vlastnosti knihovny, ne samotné syntaxe JSON.

JSON Schema v JsonSchema.Net

Program.cs — API s BuildOptions podle současné dokumentace JsonSchema.Net
using System.Text.Json;
using Json.Schema;
using var schemaText = JsonDocument.Parse("""
{"$schema":"https://json-schema.org/draft/2020-12/schema",
 "type":"object","required":["id"],
 "properties":{"id":{"type":"string"}},"additionalProperties":false}
""");
var schema = JsonSchema.Build(schemaText.RootElement,
    new BuildOptions { Dialect = Dialect.Draft202012 });
foreach (var text in new[] {"{\"id\":\"001\"}", "{\"id\":1}"})
{
    using var data = JsonDocument.Parse(text);
    var result = schema.Evaluate(data.RootElement);
    Console.WriteLine(result.IsValid);
}

Založ konzolový projekt dotnet new console -n SchemaDemo, v něm spusť dotnet add package JsonSchema.Net --version 9.4.0, nahraď Program.cs ukázkou a spusť dotnet run. Výstup je True a False. Příklad používá novější build/evaluate API a výslovně dialekt 2020-12; starší návody s FromText mohou patřit k jiné hlavní verzi knihovny.

JsonDocument/JsonElement dávají strom hodnot. Nejsou XDocument a nelze na ně použít XML XPath. Po validaci můžeš deserializovat do konkrétní třídy System.Text.Json, ale typovaná deserializace sama neověří všechna JSON Schema pravidla. Meta schéma, registrace externích schémat a formáty musí odpovídat konfiguraci validátoru. Zdroj API: JsonSchema.Net Basics.

Newtonsoft.Json a System.Text.Json

System.Text.Json je součástí .NET od verze 3.0 a používá ho ASP.NET Core. Newtonsoft.Json (Json.NET) je samostatný balíček NuGet, který mnoho projektů používá dál. Microsoft výslovně uvádí, že System.Text.Json se zaměřuje na výkon, bezpečnost a shodu se standardem a nemá cíl dosáhnout stejné sady funkcí. Migrace proto není povinná. Vyplatí se u nového kódu, u ASP.NET Core a tam, kde chceš méně závislostí nebo source generator.

Newtonsoft.JsonSystem.Text.Json
[JsonProperty("id")][JsonPropertyName("id")]
[JsonProperty(Required = Required.Always)][JsonRequired] nebo modifikátor required
[JsonIgnore], NullValueHandling.Ignore[JsonIgnore], [JsonIgnore(Condition = WhenWritingNull)]
[JsonConverter(typeof(…))][JsonConverter(typeof(…))] — jiné rozhraní konvertoru
[JsonExtensionData][JsonExtensionData]
MissingMemberHandling.Error[JsonUnmappedMemberHandling(Disallow)] (.NET 8+)
TypeNameHandling[JsonPolymorphic] a [JsonDerivedType] s výslovným seznamem typů (.NET 7+)
ReferenceLoopHandling.IgnoreReferenceHandler.IgnoreCycles (cyklus zapíše jako null)
JObject, JTokenJsonNode (měnitelný), JsonDocument / JsonElement (jen čtení)
JsonConvert.PopulateObjectnení; jen JsonObjectCreationHandling.Populate pro vlastnosti (.NET 8+)

Největší rozdíly nejsou v názvech atributů, ale ve výchozím chování. Kód po přepsání atributů zkompiluje, a přesto čte jiná data:

  • Velikost písmen. Newtonsoft páruje názvy vlastností bez ohledu na velikost písmen, System.Text.Json přesně. ASP.NET Core používá JsonSerializerDefaults.Web (camelCase, bez ohledu na velikost písmen, čísla i v uvozovkách), samostatný JsonSerializer ne.
  • Přísnější syntaxe. Komentáře, koncové čárky a apostrofy System.Text.Json odmítne, protože je RFC 8259 nedovoluje. Povolit je lze volbami ReadCommentHandling a AllowTrailingCommas.
  • Escapování. System.Text.Json ve výchozím stavu escapuje všechny znaky mimo ASCII a znaky citlivé v HTML. Výsledek je platný a rovnocenný JSON, jen hůř čitelný.
  • Polymorfismus. TypeNameHandling v Newtonsoftu nastavené na jinou hodnotu než None načte typ uvedený ve vstupu, což je u nedůvěryhodných dat známé riziko. System.Text.Json přijme jen typy, které vyjmenuješ v [JsonDerivedType].
Výchozí a webové nastavení, neznámé a chybějící pole (.NET 10)
public class Volna { public string? Id { get; set; } }

var vstup = """{"ID":"k2"}""";
JsonSerializer.Deserialize<Volna>(vstup)!.Id;                     // null
JsonSerializer.Deserialize<Volna>(vstup,
    new JsonSerializerOptions(JsonSerializerDefaults.Web))!.Id;   // "k2"

JsonSerializer.Deserialize<Kniha>("""{"id":"k3","autori":[],"admin":true}""");
// JsonException: The JSON property 'admin' could not be mapped to any .NET member
//                contained in type 'Kniha'.
JsonSerializer.Deserialize<Kniha>("""{"nazev":"X","autori":[]}""");
// JsonException: JSON deserialization for type 'Kniha' was missing required
//                properties including: 'id'.

Zdroj: Migrate from Newtonsoft.Json to System.Text.Json.

Jedna třída pro JSON i XML

Atributy System.Text.Json a XmlSerializer se navzájem neovlivňují, takže jedna třída může mít mapování pro oba formáty. Každý serializer čte jen svoje atributy:

Program.cs — .NET 10, dotnet run
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;
using System.Xml.Serialization;

var kniha = new Kniha { Id = "k1", Nazev = "Žluťoučký kůň", Cena = 199.90m, Autori = ["Jana", "Petr"] };

Console.WriteLine(JsonSerializer.Serialize(kniha));
var citelne = new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) };
Console.WriteLine(JsonSerializer.Serialize(kniha, citelne));

var xml = new XmlSerializer(typeof(Kniha));
var ns = new XmlSerializerNamespaces(); ns.Add("", "");      // bez xmlns:xsi a xmlns:xsd
using var w = new StringWriter();
xml.Serialize(w, kniha, ns);
Console.WriteLine(w);

[XmlRoot("kniha")]
[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
public class Kniha
{
    [JsonPropertyName("id"), XmlAttribute("id")]
    public required string Id { get; set; }

    [JsonPropertyName("nazev"), XmlElement("nazev")]
    public string Nazev { get; set; } = "";

    [JsonPropertyName("cena"), XmlElement("cena")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public decimal? Cena { get; set; }

    [JsonPropertyName("autori"), XmlArray("autori"), XmlArrayItem("autor")]
    public List<string> Autori { get; set; } = [];
}
Výstup
{"id":"k1","nazev":"\u017Dlu\u0165ou\u010Dk\u00FD k\u016F\u0148","cena":199.90,"autori":["Jana","Petr"]}
{"id":"k1","nazev":"Žluťoučký kůň","cena":199.90,"autori":["Jana","Petr"]}
<?xml version="1.0" encoding="utf-16"?>
<kniha id="k1">
  <nazev>Žluťoučký kůň</nazev>
  <cena>199.90</cena>
  <autori>
    <autor>Jana</autor>
    <autor>Petr</autor>
  </autori>
</kniha>
  • Diakritika. První řádek je výchozí výstup s Ž místo „Ž“. Druhý vznikl s JavaScriptEncoder.Create(UnicodeRanges.All). Ten dál escapuje znaky citlivé v HTML; UnsafeRelaxedJsonEscaping je nepoužívej, pokud JSON vkládáš do HTML stránky.
  • encoding="utf-16". StringWriter je řetězec v .NET, tedy UTF-16, a deklarace to tak uvádí. Pro soubor nebo HTTP použij XmlWriter.Create(stream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }), jinak se skutečné a deklarované kódování rozejdou.
  • Kolekce. V JSON je seznam pole, v XML obalový element autori s opakovaným autor. Bez XmlArray a XmlArrayItem by se jmenovaly podle vlastnosti a typu (Autori, string).
  • Povinnost a neznámá pole platí jen pro JSON. XmlSerializer neznámé elementy tiše přeskočí a modifikátor required nekontroluje. Pro XML použij validaci proti XSD.

Pro rychlejší start a nasazení s Native AOT vygeneruje System.Text.Json serializační kód při kompilaci:

Source generator
[JsonSerializable(typeof(Kniha))]
public partial class KnihyJson : JsonSerializerContext { }

string text = JsonSerializer.Serialize(kniha, KnihyJson.Default.Kniha);