Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo illustra come usare il tipo Utf8JsonReader per la creazione di parser e deserializzatori personalizzati.
Utf8JsonReader è un lettore a scorrimento solo in avanti con prestazioni elevate per il testo JSON con codifica UTF-8. Il testo viene letto da un ReadOnlySpan<byte> o ReadOnlySequence<byte>.
Utf8JsonReader è un tipo di basso livello che può essere usato per creare parser e deserializzatori personalizzati. (I JsonSerializer.Deserialize metodi usano Utf8JsonReader sotto il cofano.)
Nell'esempio seguente viene illustrato come utilizzare la classe Utf8JsonReader. Questo codice presuppone che la jsonUtf8Bytes variabile sia una matrice di byte che contiene codice JSON valido, codificato come 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
Nota
Utf8JsonReader non può essere usato direttamente dal codice Visual Basic. Per ottenere ulteriori informazioni, consultare l'articolo Supporto di Visual Basic.
Filtrare i dati usando Utf8JsonReader
Nel seguente esempio viene illustrato come leggere un file in modo sincrono e cercare un valore.
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
Il codice precedente:
Si presuppone che il codice JSON contenga una matrice di oggetti e ogni oggetto possa contenere una proprietà "name" di tipo string.
Conta gli oggetti e i valori delle proprietà "nome" che terminano con "University".
Presuppone che il file sia codificato come UTF-16 e lo transcodifica in UTF-8.
Un file codificato come UTF-8 può essere letto direttamente in un
ReadOnlySpan<byte>usando il seguente codice:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);Se il file contiene un byte order mark UTF-8 (BOM), rimuoverlo prima di passare i byte al
Utf8JsonReader, poiché il lettore si aspetta del testo. In caso contrario, il BOM viene considerato JSON non valido e il lettore genera un'eccezione.
Ecco un esempio JSON che il codice precedente può leggere. Il messaggio di riepilogo risultante è "2 su 4 hanno nomi che terminano con 'University'":
[
{
"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"
}
]
Suggerimento
Per una versione asincrona di questo esempio, vedere Progetto JSON di esempi .NET.
Leggere da un flusso usando Utf8JsonReader
Quando si legge un file di grandi dimensioni (ad esempio, un gigabyte o più dimensioni), è possibile evitare di caricare l'intero file in memoria contemporaneamente. Per questo scenario, è possibile usare un FileStream.
Quando si usa Utf8JsonReader per leggere da un flusso, si applicano le seguenti regole:
- Il buffer contenente il payload JSON parziale deve essere grande almeno quanto il token JSON più grande al suo interno, in modo che il lettore possa avanzare.
- Il buffer deve essere grande almeno quanto la più grande sequenza di spazi vuoti all'interno del JSON.
- Il lettore non tiene traccia dei dati letti finché non legge completamente il TokenType successivo nel payload JSON. Quindi, quando sono presenti byte rimanenti nel buffer, è necessario passarli di nuovo al lettore. È possibile usare BytesConsumed per determinare il numero di byte rimanenti.
Il codice seguente illustra come leggere da un flusso. L'esempio mostra un MemoryStream. Un codice simile funzionerà con un FileStream, tranne quando FileStream contiene un BOM UTF-8 all'inizio. In tal caso, è necessario rimuovere questi tre byte dal buffer prima di passare i byte rimanenti al Utf8JsonReader. In caso contrario, il lettore genera un'eccezione, poiché il BOM non è considerato una parte valida del JSON.
Il codice di esempio inizia con un buffer di 4 kB e raddoppia la dimensione del buffer ogni volta che scopre che la dimensione non è sufficiente per inserire un token JSON completo, necessario al lettore per avanzare nel payload JSON. L'esempio di JSON fornito nel frammento attiva un aumento delle dimensioni del buffer solo se si impostano dimensioni del buffer iniziali molto ridotte, ad esempio 10 byte. Se si imposta la dimensione iniziale del buffer su 10, le istruzioni Console.WriteLine illustrano la causa e l'effetto dell'aumento delle dimensioni del buffer. Con le dimensioni iniziali del buffer di 4 KB, l'intero codice JSON di esempio viene visualizzato da ogni chiamata a Console.WriteLinee le dimensioni del buffer non devono mai essere aumentate.
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
Nell'esempio precedente non viene impostato alcun limite alla dimensione del buffer. Se le dimensioni del token sono troppo grandi, il codice potrebbe non riuscire con un'eccezione OutOfMemoryException. Ciò può accadere se il JSON contiene un token di dimensioni pari o superiori a 1 GB, perché raddoppiando la dimensione di 1 GB si ottiene una dimensione troppo grande per essere inserita in un buffer int32.
limiti di ref struct
Poiché il Utf8JsonReader tipo è un ref struct, presenta alcune limitazioni. Ad esempio, non può essere archiviato come campo in una classe o uno struct diverso da .ref struct
Per ottenere prestazioni elevate, Utf8JsonReader è modificabile poiché contiene uno stato. Di conseguenza, è necessario passarlo per riferimento anziché per valore. Il passaggio per valore di Utf8JsonReader comporta una copia dello struct e le modifiche allo stato non saranno visibili al chiamante.
Per ottenere ulteriori informazioni su come usare i ref struct, vedere Evitare le allocazioni.
Leggere testo UTF-8
Per ottenere prestazioni ottimali durante l'uso di Utf8JsonReader, leggere i payload JSON già codificati come testo UTF-8 anziché come stringhe UTF-16. Per un esempio di codice, vedere Filtrare i dati usando Utf8JsonReader.
Lettura con multi segmento ReadOnlySequence
Se l'input JSON è un byte ReadOnlySpan<>, è possibile accedere a ogni elemento JSON dalla proprietà ValueSpan nel lettore durante il ciclo di lettura. Tuttavia, se l'input è un ReadOnlySequence< byte> (che è il risultato della lettura da un PipeReader), alcuni elementi JSON potrebbero attraversare più segmenti dell'oggetto ReadOnlySequence<byte>. Questi elementi non sarebbero accessibili da ValueSpan in un blocco di memoria contiguo. Al contrario, ogni volta che si ha un multi segmento ReadOnlySequence<byte> come input, è necessario eseguire il polling della proprietà HasValueSequence sul lettore per capire come accedere all'elemento JSON corrente. Si riporta di seguito un modello consigliato:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Leggere più documenti JSON
In .NET 9 e versioni successive è possibile leggere più documenti JSON separati da spazi vuoti da un singolo buffer o flusso. Per impostazione predefinita, Utf8JsonReader genera un'eccezione se rileva qualsiasi carattere non di spazi bianchi che segue il primo documento di alto livello. Tuttavia, è possibile configurare tale comportamento usando il JsonReaderOptions.AllowMultipleValues flag .
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
Quando AllowMultipleValues è impostato su true, è anche possibile leggere JSON dai payload che contengono dati finali che non rientrano nello standard JSON valido.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
Per trasmettere più valori di primo livello, usare l'overload DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) o DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) . Per impostazione predefinita, DeserializeAsyncEnumerable tenta di trasmettere elementi contenuti in una singola matrice JSON di primo livello. Passare true per il topLevelValues parametro per trasmettere più valori di primo livello.
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
*/
Ricerche dei nomi delle proprietà
Per cercare i nomi delle proprietà, non usare ValueSpan per eseguire confronti byte per byte chiamando SequenceEqual. Invece, chiamare ValueTextEquals, perché questo metodo annulla l'escape di tutti i caratteri che sono scappati nel JSON. Ecco un esempio che mostra come cercare una proprietà denominata "nome":
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;
}
}
Leggere i valori null in tipi di valore nullable
Le API integrate System.Text.Json restituiscono solo tipi valore non nullabili. Ad esempio, Utf8JsonReader.GetBoolean restituisce un bool. Lancia un'eccezione se trova Null nel JSON. Gli esempi seguenti illustrano due modi per gestire i valori null: restituire un tipo di valore nullable oppure restituire il valore predefinito.
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();
}
Ignorare gli elementi figlio del token
Usare il metodo Utf8JsonReader.Skip() per ignorare i sottoelementi del token JSON corrente. Se il tipo di token è JsonTokenType.PropertyName, il lettore passa al valore della proprietà. Il frammento di codice seguente mostra un esempio dell’utilizzo di Utf8JsonReader.Skip() per spostare il lettore nel valore di una proprietà.
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;
}
}
Utilizzare stringhe JSON decodificate
A partire da .NET 7, è possibile usare il metodo Utf8JsonReader.CopyString anziché Utf8JsonReader.GetString() per usare una stringa JSON decodificata. A differenza di GetString(), che alloca sempre una nuova stringa, CopyString consente di copiare la stringa senza caratteri di escape in un buffer di cui si è proprietari. Il frammento di codice seguente mostra un esempio di utilizzo di una stringa UTF-16 usando 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)
{
// ...
}
API correlate
Per deserializzare un tipo personalizzato da un'istanza di
Utf8JsonReader, chiamare JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions) o JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>). Per un esempio, vedere Deserialize da UTF-8.JsonNode e le classi che derivano da esso forniscono la possibilità di creare un DOM modificabile. È possibile convertire un'istanza di
Utf8JsonReaderin unJsonNodechiamando JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>). Il frammento di codice seguente mostra un esempio.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 consente di compilare un DOM di sola lettura tramite
Utf8JsonReader. Chiamare il metodo JsonDocument.ParseValue(Utf8JsonReader) per analizzare unJsonDocumentda un'istanza diUtf8JsonReader. È possibile accedere agli elementi JSON che compongono il payload tramite il tipo JsonElement. Ad esempio di codice che usa JsonDocument.ParseValue(Utf8JsonReader), vedere RoundtripDataTable.cs e il frammento di codice in Deserializzare i tipi dedotti nelle proprietà dell'oggetto.È anche possibile analizzare un'istanza di
Utf8JsonReaderin un JsonElement, che rappresenta un valore JSON specifico chiamando JsonElement.ParseValue(Utf8JsonReader).
Vedi anche
- Come usare Utf8JsonWriter in System.Text.Json