Jak používat Utf8JsonReader v System.Text.Json
Tento článek ukazuje, jak můžete použít Utf8JsonReader typ pro vytváření vlastních analyzátorů a deserializérů.
Utf8JsonReader je vysoce výkonná, nízká alokace, čtečka určená jen pro předávání textu JSON s kódováním UTF-8. Text se čte z nebo ReadOnlySpan<byte>
ReadOnlySequence<byte>
. Utf8JsonReader
je typ nízké úrovně, který lze použít k vytváření vlastních analyzátorů a deserializérů. (Metody JsonSerializer.Deserialize používané Utf8JsonReader
pod kryty.)
Následující příklad ukazuje, jak používat Utf8JsonReader třídu. Tento kód předpokládá, že jsonUtf8Bytes
proměnná je bajtové pole, které obsahuje platný kód JSON kódovaný jako UTF-8.
var options = new JsonReaderOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
};
var reader = new Utf8JsonReader(jsonUtf8Bytes, options);
while (reader.Read())
{
Console.Write(reader.TokenType);
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
case JsonTokenType.String:
{
string? text = reader.GetString();
Console.Write(" ");
Console.Write(text);
break;
}
case JsonTokenType.Number:
{
int intValue = reader.GetInt32();
Console.Write(" ");
Console.Write(intValue);
break;
}
// Other token types elided for brevity
}
Console.WriteLine();
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Poznámka:
Utf8JsonReader
nelze použít přímo z kódu jazyka Visual Basic. Další informace naleznete v tématu Podpora jazyka Visual Basic.
Filtrování dat pomocí Utf8JsonReader
Následující příklad ukazuje, jak synchronně číst soubor a hledat hodnotu.
using System.Text;
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class Utf8ReaderFromFile
{
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
private static ReadOnlySpan<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };
public static void Run()
{
// ReadAllBytes if the file encoding is UTF-8:
string fileName = "UniversitiesUtf8.json";
ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
// Read past the UTF-8 BOM bytes if a BOM exists.
if (jsonReadOnlySpan.StartsWith(Utf8Bom))
{
jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length);
}
// Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan<byte>
//string fileName = "Universities.json";
//string jsonString = File.ReadAllText(fileName);
//ReadOnlySpan<byte> jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString);
int count = 0;
int total = 0;
var reader = new Utf8JsonReader(jsonReadOnlySpan);
while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
// Assume valid JSON, known schema
reader.Read();
if (reader.GetString()!.EndsWith("University"))
{
count++;
}
}
break;
}
}
Console.WriteLine($"{count} out of {total} have names that end with 'University'");
}
}
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Předchozí kód:
Předpokládá, že JSON obsahuje pole objektů a každý objekt může obsahovat vlastnost "name" typu string.
Počítá objekty a hodnoty vlastností name, které končí na "University".
Předpokládá, že soubor je kódovaný jako UTF-16 a překóduje ho do UTF-8.
Soubor kódovaný jako UTF-8 lze číst přímo do souboru
ReadOnlySpan<byte>
pomocí následujícího kódu:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
Pokud soubor obsahuje značku pořadí bajtů UTF-8 (BOM), odeberte ho před předáním bajtů do objektu
Utf8JsonReader
, protože čtenář očekává text. V opačném případě se bom považuje za neplatný JSON a čtenář vyvolá výjimku.
Tady je ukázka JSON, kterou může předchozí kód přečíst. Výsledná souhrnná zpráva je "2 z 4 jmen, které končí na "Univerzita":
[
{
"web_pages": [ "https://contoso.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contoso.edu" ],
"name": "Contoso Community College"
},
{
"web_pages": [ "http://fabrikam.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikam.edu" ],
"name": "Fabrikam Community College"
},
{
"web_pages": [ "http://www.contosouniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contosouniversity.edu" ],
"name": "Contoso University"
},
{
"web_pages": [ "http://www.fabrikamuniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikamuniversity.edu" ],
"name": "Fabrikam University"
}
]
Tip
Asynchronní verzi tohoto příkladu najdete v ukázkách projektu JSON s ukázkami .NET.
Čtení ze streamu pomocí Utf8JsonReader
Při čtení velkého souboru (například gigabajtu nebo více), můžete se vyhnout načtení celého souboru do paměti najednou. Pro tento scénář můžete použít .FileStream
Při použití Utf8JsonReader
čtení z datového proudu platí následující pravidla:
- Vyrovnávací paměť obsahující částečnou datovou část JSON musí být alespoň tak velká jako největší token JSON v ní, aby čtenář mohl pokračovat.
- Vyrovnávací paměť musí být alespoň tak velká jako největší posloupnost prázdných znaků v rámci JSON.
- Čtenář nemá přehled o datech, která čte, dokud nepřečte další TokenType data v datové části JSON. Takže když v vyrovnávací paměti zůstanou bajty, musíte je znovu předat čtenáři. Můžete použít BytesConsumed k určení, kolik bajtů zbývá.
Následující kód ukazuje, jak číst z datového proudu. Příklad ukazuje .MemoryStream Podobný kód bude fungovat s FileStreamvýjimkou případů FileStream
, kdy obsahuje kusovník UTF-8 na začátku. V takovém případě je nutné tyto tři bajty z vyrovnávací paměti odstranit před předáním zbývajících bajtů do Utf8JsonReader
. V opačném případě by čtečka vyvolala výjimku, protože kusovník se nepovažuje za platnou část JSON.
Vzorový kód začíná vyrovnávací pamětí o velikosti 4 kB a pokaždé, když zjistí, že velikost není dostatečně velká, aby se vešla do kompletního tokenu JSON, který je nutný pro čtenáře, aby mohl pokračovat v datové části JSON. Ukázka JSON poskytnutá ve fragmentu kódu aktivuje zvětšení velikosti vyrovnávací paměti pouze v případě, že nastavíte velmi malou počáteční velikost vyrovnávací paměti, například 10 bajtů. Pokud nastavíte počáteční velikost vyrovnávací paměti na 10, Console.WriteLine
příkazy znázorňují příčinu a vliv zvětšení velikosti vyrovnávací paměti. Při počáteční velikosti vyrovnávací paměti 4 kB se při každém volání Console.WriteLine
zobrazí celý ukázkový kód JSON a velikost vyrovnávací paměti se nikdy nemusí zvětšit.
using System.Text;
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class Utf8ReaderPartialRead
{
public static void Run()
{
var jsonString = @"{
""Date"": ""2019-08-01T00:00:00-07:00"",
""Temperature"": 25,
""TemperatureRanges"": {
""Cold"": { ""High"": 20, ""Low"": -10 },
""Hot"": { ""High"": 60, ""Low"": 20 }
},
""Summary"": ""Hot"",
}";
byte[] bytes = Encoding.UTF8.GetBytes(jsonString);
var stream = new MemoryStream(bytes);
var buffer = new byte[4096];
// Fill the buffer.
// For this snippet, we're assuming the stream is open and has data.
// If it might be closed or empty, check if the return value is 0.
stream.Read(buffer);
// We set isFinalBlock to false since we expect more data in a subsequent read from the stream.
var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default);
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
// Search for "Summary" property name
while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary"))
{
if (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
}
// Found the "Summary" property name.
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
while (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}
private static void GetMoreBytesFromStream(
MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader reader)
{
int bytesRead;
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> leftover = buffer.AsSpan((int)reader.BytesConsumed);
if (leftover.Length == buffer.Length)
{
Array.Resize(ref buffer, buffer.Length * 2);
Console.WriteLine($"Increased buffer size to {buffer.Length}");
}
leftover.CopyTo(buffer);
bytesRead = stream.Read(buffer.AsSpan(leftover.Length));
}
else
{
bytesRead = stream.Read(buffer);
}
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState);
}
}
}
' This code example doesn't apply to Visual Basic. For more information, go to the following URL:
' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Předchozí příklad nenastavuje žádné omezení, jak velká vyrovnávací paměť může růst. Pokud je velikost tokenu příliš velká, může kód selhat s OutOfMemoryException výjimkou. K tomu může dojít v případě, že JSON obsahuje token, který má velikost přibližně 1 GB nebo větší, protože při zdvojnásobení velikosti 1 GB dojde k příliš velké velikosti, aby se vešla do int32
vyrovnávací paměti.
omezení struktury ref
Vzhledem k tomu, že Utf8JsonReader
typ je , ref struct
má určitá omezení. Nelze ji například uložit jako pole v jiné třídě nebo struktuře než ref struct
.
Aby bylo možné dosáhnout vysokého výkonu, Utf8JsonReader
musí být , ref struct
protože musí ukládat vstupní bajt> ReadOnlySpan<(což je ref struct
sám ). Kromě toho je typ proměnlivý, Utf8JsonReader
protože obsahuje stav. Proto ji předejte odkazem místo podle hodnoty. Utf8JsonReader
Předáním hodnoty by došlo k zkopírování struktury a změny stavu by volajícímu nebylo viditelné.
Další informace o tom, jak používat ref struktury, naleznete v tématu Vyhněte se přidělení.
Čtení textu UTF-8
Pokud chcete dosáhnout nejlepšího možného výkonu při použití Utf8JsonReader
, přečtěte si datové části JSON, které jsou už kódované jako text UTF-8, a ne jako řetězce UTF-16. Příklad kódu naleznete v tématu Filtrování dat pomocí Utf8JsonReader.
Čtení pomocí funkce ReadOnlySequence s více segmenty
Pokud je vstupEM JSON bajt> ReadOnlySpan<, každý prvek JSON je přístupný z ValueSpan
vlastnosti čtenáře při procházení smyčky čtení. Pokud je ale vaším vstupem bajt> ReadOnlySequence<(což je výsledek čtení z aPipeReader), můžou některé prvky JSON řadit více segmentů objektu ReadOnlySequence<byte>
. Tyto prvky by nebyly přístupné z ValueSpan souvislého bloku paměti. Místo toho pokaždé, když jako vstup máte více segmentů ReadOnlySequence<byte>
, dotazujte HasValueSequence se na vlastnost čtenáře a zjistěte, jak získat přístup k aktuálnímu prvku JSON. Tady je doporučený vzor:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Čtení více dokumentů JSON
V .NET 9 a novějších verzích můžete číst více dokumentů JSON oddělených prázdnými znaky z jedné vyrovnávací paměti nebo datového proudu. Ve výchozím nastavení vyvolá výjimku, pokud zjistí všechny znaky, které nejsou prázdné znaky, Utf8JsonReader
které na konci prvního dokumentu nejvyšší úrovně. Toto chování ale můžete nakonfigurovat pomocí příznaku JsonReaderOptions.AllowMultipleValues .
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("null {} 1 \r\n [1,2,3]"u8, options);
reader.Read();
Console.WriteLine(reader.TokenType); // Null
reader.Read();
Console.WriteLine(reader.TokenType); // StartObject
reader.Skip();
reader.Read();
Console.WriteLine(reader.TokenType); // Number
reader.Read();
Console.WriteLine(reader.TokenType); // StartArray
reader.Skip();
Console.WriteLine(reader.Read()); // False
Pokud AllowMultipleValues je nastavená hodnota true
, můžete také číst JSON z datových částí, které obsahují koncová data, která jsou neplatná.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
Pokud chcete streamovat více hodnot nejvyšší úrovně, použijte DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) hodnotu nebo DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) přetížení. Ve výchozím nastavení DeserializeAsyncEnumerable
se pokusí streamovat prvky, které jsou obsaženy v jednom poli JSON nejvyšší úrovně. Předání true
parametru topLevelValues
pro streamování více hodnot nejvyšší úrovně.
ReadOnlySpan<byte> utf8Json = """[0] [0,1] [0,1,1] [0,1,1,2] [0,1,1,2,3]"""u8;
using var stream = new MemoryStream(utf8Json.ToArray());
var items = JsonSerializer.DeserializeAsyncEnumerable<int[]>(stream, topLevelValues: true);
await foreach (int[] item in items)
{
Console.WriteLine(item.Length);
}
/* This snippet produces the following output:
*
* 1
* 2
* 3
* 4
* 5
*/
Vyhledávání názvů vlastností
Pokud chcete vyhledat názvy vlastností, nepoužívejte ValueSpan k porovnávání bajtů bajty voláním SequenceEqual. Místo toho volejte ValueTextEquals, protože tato metoda unescapes všechny znaky, které jsou řídicími znaky ve formátu JSON. Tady je příklad, který ukazuje, jak vyhledat vlastnost s názvem "name":
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
count++;
}
break;
}
}
Čtení hodnot null do typů hodnot s možnou hodnotou null
Integrovaná System.Text.Json
rozhraní API vrací pouze typy hodnot, které nemají hodnotu null. Například Utf8JsonReader.GetBoolean vrátí hodnotu bool
. Vyvolá výjimku, pokud ji najde Null
ve formátu JSON. Následující příklady ukazují dva způsoby zpracování hodnot null, jeden vrácením typu hodnoty s možnou hodnotou null a druhý vrácením výchozí hodnoty:
public bool? ReadAsNullableBoolean()
{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}
public bool ReadAsBoolean(bool defaultValue)
{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return defaultValue;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}
Přeskočit podřízené položky tokenu
Utf8JsonReader.Skip() Pomocí metody můžete přeskočit podřízené položky aktuálního tokenu JSON. Pokud je JsonTokenType.PropertyNametyp tokenu , čtenář se přesune na hodnotu vlastnosti. Následující fragment kódu ukazuje příklad použití Utf8JsonReader.Skip() k přesunutí čtenáře na hodnotu vlastnosti.
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast);
var reader = new Utf8JsonReader(jsonUtf8Bytes);
int temp;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
{
if (reader.ValueTextEquals("TemperatureCelsius"))
{
reader.Skip();
temp = reader.GetInt32();
Console.WriteLine($"Temperature is {temp} degrees.");
}
continue;
}
default:
continue;
}
}
Využívání dekódovaných řetězců JSON
Počínaje rozhraním .NET 7 můžete místo použití dekódovaného řetězce JSON použít Utf8JsonReader.CopyString metodu Utf8JsonReader.GetString() . Na rozdíl od GetString()toho, který vždy přidělí nový řetězec, CopyString umožňuje zkopírovat unescaped řetězec do vyrovnávací paměti, kterou vlastníte. Následující fragment kódu ukazuje příklad využívání řetězce UTF-16 pomocí CopyString.
var reader = new Utf8JsonReader( /* jsonReadOnlySpan */ );
int valueLength = reader.HasValueSequence
? checked((int)reader.ValueSequence.Length)
: reader.ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.AsSpan(0, charsRead);
// Handle the unescaped JSON string.
ParseUnescapedString(source);
ArrayPool<char>.Shared.Return(buffer, clearArray: true);
void ParseUnescapedString(ReadOnlySpan<char> source)
{
// ...
}
Související rozhraní API
Chcete-li deserializovat vlastní typ z
Utf8JsonReader
instance, volání JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions) nebo JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>). Příklad naleznete v tématu Deserialize z UTF-8.JsonNode a třídy, které jsou z ní odvozeny, poskytují možnost vytvořit proměnlivý DOM. Instanci můžete převést
Utf8JsonReader
naJsonNode
volání JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>). Následující fragment kódu ukazuje příklad.using System.Text.Json; using System.Text.Json.Nodes; namespace Utf8ReaderToJsonNode { public class WeatherForecast { public DateTimeOffset Date { get; set; } public int TemperatureCelsius { get; set; } public string? Summary { get; set; } } public class Program { public static void Main() { var weatherForecast = new WeatherForecast { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" }; byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast); var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes); JsonNode? node = JsonNode.Parse(ref utf8Reader); Console.WriteLine(node); } } }
JsonDocument poskytuje možnost vytvořit dom jen pro čtení pomocí
Utf8JsonReader
. JsonDocument.ParseValue(Utf8JsonReader) Voláním metody parsujteJsonDocument
zUtf8JsonReader
instance. K elementům JSON, které tvoří datovou část, můžete přistupovat prostřednictvím JsonElement typu. Příklad kódu, který používá JsonDocument.ParseValue(Utf8JsonReader), viz RoundtripDataTable.cs a fragment kódu v Deserialize odvozené typy na vlastnosti objektu.Můžete také analyzovat
Utf8JsonReader
instanci na , JsonElementkterá představuje konkrétní hodnotu JSON voláním JsonElement.ParseValue(Utf8JsonReader).