Procedimiento para usar un documento JSON, Utf8JsonReader y Utf8JsonWriter en System.Text.Json
En este artículo se muestra cómo usar:
- Un modelo de objetos de documento (DOM) JSON para el acceso aleatorio a los datos de una carga JSON.
- El tipo
Utf8JsonWriter
para compilar serializadores personalizados. - El tipo
Utf8JsonReader
para compilar analizadores y deserializadores personalizados.
Elecciones de DOM JSON
El trabajo con un DOM es una alternativa a la deserialización con JsonSerializer:
- Si no se tiene un tipo en el que deserializar.
- Si el JSON que se recibe no tiene un esquema fijo y hay que inspeccionarlo para saber lo que contiene.
System.Text.Json
proporciona dos maneras de compilar un DOM JSON:
- JsonDocument ofrece la posibilidad de compilar un DOM de solo lectura mediante
Utf8JsonReader
. A los elementos JSON que componen la carga se puede acceder mediante el tipo JsonElement. El tipoJsonElement
contiene los enumeradores de matriz y objeto junto con las API para convertir texto JSON en tipos de .NET comunes.JsonDocument
expone una propiedad RootElement. Para obtener más información, vea Uso de JsonDocument más adelante en este artículo.
- JsonNode y las clases que derivan de él en el espacio de nombres System.Text.Json.Nodes ofrecen la posibilidad de crear un DOM mutable. A los elementos JSON que componen la carga se puede acceder mediante los tipos JsonNode, JsonObject, JsonArray, JsonValue y JsonElement. Para obtener más información, vea Uso de
JsonNode
más adelante en este artículo.
Tenga en cuenta los siguientes factores a la hora de elegir entre JsonDocument
y JsonNode
:
- El DOM
JsonNode
se puede cambiar después de su creación. El DOMJsonDocument
es inmutable. - El DOM
JsonDocument
proporciona un acceso más rápido a sus datos.
- A partir de .NET 6, JsonNode y las clases que derivan de él en el espacio de nombres System.Text.Json.Nodes ofrecen la posibilidad de crear un DOM mutable. Para obtener más información, vea la versión de .NET 6 de este artículo.
Use JsonNode
En el ejemplo siguiente se muestra cómo usar JsonNode y los otros tipos del espacio de nombres System.Text.Json.Nodes para:
- Crear un DOM a partir de una cadena JSON
- Escribir JSON desde un DOM.
- Obtener un valor, un objeto o una matriz de un DOM.
using System.Text.Json;
using System.Text.Json.Nodes;
namespace JsonNodeFromStringExample;
public class Program
{
public static void Main()
{
string jsonString =
@"{
""Date"": ""2019-08-01T00:00:00"",
""Temperature"": 25,
""Summary"": ""Hot"",
""DatesAvailable"": [
""2019-08-01T00:00:00"",
""2019-08-02T00:00:00""
],
""TemperatureRanges"": {
""Cold"": {
""High"": 20,
""Low"": -10
},
""Hot"": {
""High"": 60,
""Low"": 20
}
}
}
";
// Create a JsonNode DOM from a JSON string.
JsonNode forecastNode = JsonNode.Parse(jsonString)!;
// Write JSON from a JsonNode
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(forecastNode!.ToJsonString(options));
// output:
//{
// "Date": "2019-08-01T00:00:00",
// "Temperature": 25,
// "Summary": "Hot",
// "DatesAvailable": [
// "2019-08-01T00:00:00",
// "2019-08-02T00:00:00"
// ],
// "TemperatureRanges": {
// "Cold": {
// "High": 20,
// "Low": -10
// },
// "Hot": {
// "High": 60,
// "Low": 20
// }
// }
//}
// Get value from a JsonNode.
JsonNode temperatureNode = forecastNode!["Temperature"]!;
Console.WriteLine($"Type={temperatureNode.GetType()}");
Console.WriteLine($"JSON={temperatureNode.ToJsonString()}");
//output:
//Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
//JSON = 25
// Get a typed value from a JsonNode.
int temperatureInt = (int)forecastNode!["Temperature"]!;
Console.WriteLine($"Value={temperatureInt}");
//output:
//Value=25
// Get a typed value from a JsonNode by using GetValue<T>.
temperatureInt = forecastNode!["Temperature"]!.GetValue<int>();
Console.WriteLine($"TemperatureInt={temperatureInt}");
//output:
//Value=25
// Get a JSON object from a JsonNode.
JsonNode temperatureRanges = forecastNode!["TemperatureRanges"]!;
Console.WriteLine($"Type={temperatureRanges.GetType()}");
Console.WriteLine($"JSON={temperatureRanges.ToJsonString()}");
//output:
//Type = System.Text.Json.Nodes.JsonObject
//JSON = { "Cold":{ "High":20,"Low":-10},"Hot":{ "High":60,"Low":20} }
// Get a JSON array from a JsonNode.
JsonNode datesAvailable = forecastNode!["DatesAvailable"]!;
Console.WriteLine($"Type={datesAvailable.GetType()}");
Console.WriteLine($"JSON={datesAvailable.ToJsonString()}");
//output:
//datesAvailable Type = System.Text.Json.Nodes.JsonArray
//datesAvailable JSON =["2019-08-01T00:00:00", "2019-08-02T00:00:00"]
// Get an array element value from a JsonArray.
JsonNode firstDateAvailable = datesAvailable[0]!;
Console.WriteLine($"Type={firstDateAvailable.GetType()}");
Console.WriteLine($"JSON={firstDateAvailable.ToJsonString()}");
//output:
//Type = System.Text.Json.Nodes.JsonValue`1[System.Text.Json.JsonElement]
//JSON = "2019-08-01T00:00:00"
// Get a typed value by chaining references.
int coldHighTemperature = (int)forecastNode["TemperatureRanges"]!["Cold"]!["High"]!;
Console.WriteLine($"TemperatureRanges.Cold.High={coldHighTemperature}");
//output:
//TemperatureRanges.Cold.High = 20
// Parse a JSON array
var datesNode = JsonNode.Parse(@"[""2019-08-01T00:00:00"",""2019-08-02T00:00:00""]");
JsonNode firstDate = datesNode![0]!.GetValue<DateTime>();
Console.WriteLine($"firstDate={ firstDate}");
//output:
//firstDate = "2019-08-01T00:00:00"
}
}
Creación de un DOM JsonNode con inicializadores de objeto y realización de cambios
El ejemplo siguiente muestra cómo:
- Crear un DOM mediante inicializadores de objeto.
- Realizar cambios en un DOM.
using System.Text.Json;
using System.Text.Json.Nodes;
namespace JsonNodeFromObjectExample;
public class Program
{
public static void Main()
{
// Create a new JsonObject using object initializers.
var forecastObject = new JsonObject
{
["Date"] = new DateTime(2019, 8, 1),
["Temperature"] = 25,
["Summary"] = "Hot",
["DatesAvailable"] = new JsonArray(
new DateTime(2019, 8, 1), new DateTime(2019, 8, 2)),
["TemperatureRanges"] = new JsonObject
{
["Cold"] = new JsonObject
{
["High"] = 20,
["Low"] = -10
}
},
["SummaryWords"] = new JsonArray("Cool", "Windy", "Humid")
};
// Add an object.
forecastObject!["TemperatureRanges"]!["Hot"] =
new JsonObject { ["High"] = 60, ["Low"] = 20 };
// Remove a property.
forecastObject.Remove("SummaryWords");
// Change the value of a property.
forecastObject["Date"] = new DateTime(2019, 8, 3);
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(forecastObject.ToJsonString(options));
//output:
//{
// "Date": "2019-08-03T00:00:00",
// "Temperature": 25,
// "Summary": "Hot",
// "DatesAvailable": [
// "2019-08-01T00:00:00",
// "2019-08-02T00:00:00"
// ],
// "TemperatureRanges": {
// "Cold": {
// "High": 20,
// "Low": -10
// },
// "Hot": {
// "High": 60,
// "Low": 20
// }
// }
//}
}
}
Deserialización de subsecciones de una carga JSON
En el ejemplo siguiente se muestra cómo usar JsonNode para ir a una subsección de un árbol JSON y deserializar un único valor, un tipo personalizado o una matriz de esa subsección.
using System.Text.Json;
using System.Text.Json.Nodes;
namespace JsonNodePOCOExample;
public class TemperatureRanges : Dictionary<string, HighLowTemps>
{
}
public class HighLowTemps
{
public int High { get; set; }
public int Low { get; set; }
}
public class Program
{
public static DateTime[]? DatesAvailable { get; set; }
public static void Main()
{
string jsonString =
@"{
""Date"": ""2019-08-01T00:00:00"",
""Temperature"": 25,
""Summary"": ""Hot"",
""DatesAvailable"": [
""2019-08-01T00:00:00"",
""2019-08-02T00:00:00""
],
""TemperatureRanges"": {
""Cold"": {
""High"": 20,
""Low"": -10
},
""Hot"": {
""High"": 60,
""Low"": 20
}
}
}
";
// Parse all of the JSON.
JsonNode forecastNode = JsonNode.Parse(jsonString)!;
// Get a single value
int hotHigh = forecastNode["TemperatureRanges"]!["Hot"]!["High"]!.GetValue<int>();
Console.WriteLine($"Hot.High={hotHigh}");
// output:
//Hot.High=60
// Get a subsection and deserialize it into a custom type.
JsonObject temperatureRangesObject = forecastNode!["TemperatureRanges"]!.AsObject();
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
temperatureRangesObject.WriteTo(writer);
writer.Flush();
TemperatureRanges? temperatureRanges =
JsonSerializer.Deserialize<TemperatureRanges>(stream.ToArray());
Console.WriteLine($"Cold.Low={temperatureRanges!["Cold"].Low}, Hot.High={temperatureRanges["Hot"].High}");
// output:
//Cold.Low=-10, Hot.High=60
// Get a subsection and deserialize it into an array.
JsonArray datesAvailable = forecastNode!["DatesAvailable"]!.AsArray()!;
Console.WriteLine($"DatesAvailable[0]={datesAvailable[0]}");
// output:
//DatesAvailable[0]=8/1/2019 12:00:00 AM
}
}
Ejemplo de nota media de JsonNode
En el ejemplo siguiente se selecciona una matriz JSON que tiene valores enteros y se calcula un valor medio:
using System.Text.Json.Nodes;
namespace JsonNodeAverageGradeExample;
public class Program
{
public static void Main()
{
string jsonString =
@"{
""Class Name"": ""Science"",
""Teacher\u0027s Name"": ""Jane"",
""Semester"": ""2019-01-01"",
""Students"": [
{
""Name"": ""John"",
""Grade"": 94.3
},
{
""Name"": ""James"",
""Grade"": 81.0
},
{
""Name"": ""Julia"",
""Grade"": 91.9
},
{
""Name"": ""Jessica"",
""Grade"": 72.4
},
{
""Name"": ""Johnathan""
}
],
""Final"": true
}
";
double sum = 0;
int count = 0;
JsonNode document = JsonNode.Parse(jsonString)!;
JsonNode root = document.Root;
JsonArray studentsArray = root["Students"]!.AsArray();
count = studentsArray.Count;
foreach (JsonNode? student in studentsArray)
{
if (student?["Grade"] is JsonNode gradeNode)
{
sum += (double)gradeNode;
}
else
{
sum += 70;
}
}
double average = sum / count;
Console.WriteLine($"Average grade : {average}");
}
}
// output:
//Average grade : 81.92
El código anterior:
- Calcula la calificación media de los objetos de una matriz de
Students
que tienen una propiedadGrade
. - Asigna una calificación predeterminada de 70 a los alumnos que no tienen ninguna calificación.
- Obtiene el número de alumnos de la propiedad
Count
deJsonArray
.
JsonNode
con JsonSerializerOptions
Puede usar JsonSerializer
para serializar y deserializar una instancia de JsonNode
. Pero si usa una sobrecarga que toma JsonSerializerOptions
, la instancia de opciones solo se usa para obtener convertidores personalizados. No se usan otras características de la instancia de opciones. Por ejemplo, si establece JsonSerializerOptions.DefaultIgnoreCondition en WhenWritingNull y llama a JsonSerializer
con una sobrecarga que toma JsonSerializerOptions
, no se omiten las propiedades null.
La misma limitación se aplica a los métodos JsonNode
que toman un parámetro JsonSerializerOptions
: WriteTo(Utf8JsonWriter, JsonSerializerOptions) y ToJsonString(JsonSerializerOptions). Estas API solo usan JsonSerializerOptions
para obtener convertidores personalizados.
En el ejemplo siguiente se muestra el resultado del uso de métodos que toman un parámetro JsonSerializerOptions
y serializan una instancia JsonNode
:
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace JsonNodeWithJsonSerializerOptions;
public class Program
{
public static void Main()
{
Person person = new Person { Name = "Nancy" };
// Default serialization - Address property included with null token.
// Output: {"Name":"Nancy","Address":null}
string personJsonWithNull = JsonSerializer.Serialize(person);
Console.WriteLine(personJsonWithNull);
// Serialize and ignore null properties - null Address property is omitted
// Output: {"Name":"Nancy"}
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
Console.WriteLine(personJsonWithoutNull);
// Ignore null properties doesn't work when serializing JsonNode instance
// by using JsonSerializer.
// Output: {"Name":"Nancy","Address":null}
var personJsonNode = JsonSerializer.Deserialize<JsonNode>(personJsonWithNull);
personJsonWithNull = JsonSerializer.Serialize(personJsonNode, options);
Console.WriteLine(personJsonWithNull);
// Ignore null properties doesn't work when serializing JsonNode instance
// by using JsonNode.ToJsonString method.
// Output: {"Name":"Nancy","Address":null}
personJsonWithNull = personJsonNode!.ToJsonString(options);
Console.WriteLine(personJsonWithNull);
// Ignore null properties doesn't work when serializing JsonNode instance
// by using JsonNode.WriteTo method.
// Output: {"Name":"Nancy","Address":null}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
personJsonNode!.WriteTo(writer, options);
writer.Flush();
personJsonWithNull = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(personJsonWithNull);
}
}
public class Person
{
public string? Name { get; set; }
public string? Address { get; set; }
}
Si necesita características de JsonSerializerOptions
distintas a los convertidores personalizados, use JsonSerializer
con destinos fuertemente tipados (como la clase Person
de este ejemplo) en lugar de JsonNode
.
Use JsonDocument
En el ejemplo siguiente se muestra cómo usar la clase JsonDocument para el acceso aleatorio a los datos de una cadena JSON:
double sum = 0;
int count = 0;
using (JsonDocument document = JsonDocument.Parse(jsonString))
{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");
foreach (JsonElement student in studentsElement.EnumerateArray())
{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
count++;
}
}
double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
Dim root As JsonElement = document.RootElement
Dim studentsElement As JsonElement = root.GetProperty("Students")
For Each student As JsonElement In studentsElement.EnumerateArray()
Dim gradeElement As JsonElement = Nothing
If student.TryGetProperty("Grade", gradeElement) Then
sum += gradeElement.GetDouble()
Else
sum += 70
End If
count += 1
Next
End Using
Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")
El código anterior:
- Supone que el JSON que se va a analizar está en una cadena denominada
jsonString
. - Calcula la calificación media de los objetos de una matriz de
Students
que tienen una propiedadGrade
. - Asigna una calificación predeterminada de 70 a los alumnos que no tienen ninguna calificación.
- Crea la instancia
JsonDocument
enusing
(Instrucción) porqueJsonDocument
implementaIDisposable
. Después de eliminar una instanciaJsonDocument
, también se pierde el acceso a todas sus instanciasJsonElement
. Para conservar el acceso a una instanciaJsonElement
, realice una copia de ella antes de eliminar la instancia primariaJsonDocument
. Para realizar una copia, llame a JsonElement.Clone. Para obtener más información, vea JsonDocument es IDisposable.
El código de ejemplo anterior cuenta a los alumnos por medio del incremento de una variable count
con cada iteración. Una alternativa es llamar a GetArrayLength, tal y como se muestra en el ejemplo siguiente:
double sum = 0;
int count = 0;
using (JsonDocument document = JsonDocument.Parse(jsonString))
{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");
count = studentsElement.GetArrayLength();
foreach (JsonElement student in studentsElement.EnumerateArray())
{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
}
}
double average = sum / count;
Console.WriteLine($"Average grade : {average}");
Dim sum As Double = 0
Dim count As Integer = 0
Using document As JsonDocument = JsonDocument.Parse(jsonString)
Dim root As JsonElement = document.RootElement
Dim studentsElement As JsonElement = root.GetProperty("Students")
count = studentsElement.GetArrayLength()
For Each student As JsonElement In studentsElement.EnumerateArray()
Dim gradeElement As JsonElement = Nothing
If student.TryGetProperty("Grade", gradeElement) Then
sum += gradeElement.GetDouble()
Else
sum += 70
End If
Next
End Using
Dim average As Double = sum / count
Console.WriteLine($"Average grade : {average}")
Aquí se muestra un ejemplo del código JSON que este código procesa:
{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}
Para obtener un ejemplo similar que usa JsonNode
en lugar de JsonDocument
, vea Ejemplo de nota media de JsonNode.
Cómo buscar subelementos en JsonDocument y JsonElement
Las búsquedas en JsonElement
requieren una búsqueda secuencial de las propiedades y, por lo tanto, son relativamente lentas (por ejemplo, al usar TryGetProperty
). System.Text.Json está diseñado para minimizar el tiempo de análisis inicial en lugar del tiempo de búsqueda. Por lo tanto, use los enfoques siguientes para optimizar el rendimiento al buscar en un objeto JsonDocument
:
- Use los enumeradores integrados (EnumerateArray y EnumerateObject) en lugar de crear sus propios bucles o índices.
- No realice una búsqueda secuencial en todo el
JsonDocument
a través de todas las propiedades medianteRootElement
. En su lugar, busque objetos JSON anidados en función de la estructura conocida de los datos JSON. Por ejemplo, en los ejemplos de código anteriores se busca una propiedadGrade
en objetosStudent
al recorrer en bucle los objetosStudent
y obtener el valor deGrade
de cada uno, en lugar de buscar en todos los objetosJsonElement
las propiedadesGrade
. Si se hace esto último, se pasaría innecesariamente por los mismos datos.
Uso de JsonDocument
para escribir JSON
En el siguiente ejemplo se muestra cómo escribir JSON desde una JsonDocument:
string jsonString = File.ReadAllText(inputFileName);
var writerOptions = new JsonWriterOptions
{
Indented = true
};
var documentOptions = new JsonDocumentOptions
{
CommentHandling = JsonCommentHandling.Skip
};
using FileStream fs = File.Create(outputFileName);
using var writer = new Utf8JsonWriter(fs, options: writerOptions);
using JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);
JsonElement root = document.RootElement;
if (root.ValueKind == JsonValueKind.Object)
{
writer.WriteStartObject();
}
else
{
return;
}
foreach (JsonProperty property in root.EnumerateObject())
{
property.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
Dim jsonString As String = File.ReadAllText(inputFileName)
Dim writerOptions As JsonWriterOptions = New JsonWriterOptions With {
.Indented = True
}
Dim documentOptions As JsonDocumentOptions = New JsonDocumentOptions With {
.CommentHandling = JsonCommentHandling.Skip
}
Dim fs As FileStream = File.Create(outputFileName)
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(fs, options:=writerOptions)
Dim document As JsonDocument = JsonDocument.Parse(jsonString, documentOptions)
Dim root As JsonElement = document.RootElement
If root.ValueKind = JsonValueKind.[Object] Then
writer.WriteStartObject()
Else
Return
End If
For Each [property] As JsonProperty In root.EnumerateObject()
[property].WriteTo(writer)
Next
writer.WriteEndObject()
writer.Flush()
El código anterior:
- Lee un archivo JSON, carga los datos en un
JsonDocument
y escribe JSON con formato (impreso correctamente) en un archivo. - Utiliza JsonDocumentOptions para especificar que se permiten los comentarios en el JSON de entrada, pero se omiten.
- Cuando termina, llama a Flush en el escritor. Una alternativa consiste en permitir el vaciado automático del escritor cuando se elimina.
Aquí se muestra un ejemplo de entrada JSON que el código de ejemplo va a procesar:
{"Class Name": "Science","Teacher's Name": "Jane","Semester": "2019-01-01","Students": [{"Name": "John","Grade": 94.3},{"Name": "James","Grade": 81.0},{"Name": "Julia","Grade": 91.9},{"Name": "Jessica","Grade": 72.4},{"Name": "Johnathan"}],"Final": true}
El resultado es la siguiente salida JSON impresa correctamente:
{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}
JsonDocument es IDisposable
JsonDocument
compila una vista en memoria de los datos en un búfer agrupado. Por lo tanto, el tipo JsonDocument
implementa IDisposable
y debe usarse dentro de un bloque using
.
Devuelva un JsonDocument
desde la API solo si quiere transferir la propiedad de la duración y derivar la responsabilidad al autor de la llamada. En la mayoría de los escenarios, eso no es necesario. Si el autor de la llamada necesita trabajar con todo el documento JSON, devuelva el Clone del RootElement, que es un JsonElement. Si el autor de la llamada necesita trabajar con un elemento determinado dentro del documento JSON, devuelva el Clone de dicho JsonElement. Si devuelve el RootElement
o un subelemento directamente sin realizar un Clone
, el autor de la llamada no podrá acceder al JsonElement
devuelto después de que se elimine el JsonDocument
que lo posee.
Este es un ejemplo en el que se le requiere que realice un Clone
:
public JsonElement LookAndLoad(JsonElement source)
{
string json = File.ReadAllText(source.GetProperty("fileName").GetString());
using (JsonDocument doc = JsonDocument.Parse(json))
{
return doc.RootElement.Clone();
}
}
El código anterior espera un JsonElement
que contiene una propiedad fileName
. Abre el archivo JSON y crea un JsonDocument
. El método supone que el autor de la llamada quiere trabajar con todo el documento, por lo que devuelve el Clone
del RootElement
.
Si recibe un JsonElement
y está devolviendo un subelemento, no es necesario devolver un Clone
del subelemento. El autor de la llamada es responsable de mantener activo el JsonDocument
al que pertenece el JsonElement
pasado. Por ejemplo:
public JsonElement ReturnFileName(JsonElement source)
{
return source.GetProperty("fileName");
}
JsonDocument
con JsonSerializerOptions
Puede usar JsonSerializer
para serializar y deserializar una instancia de JsonDocument
. Pero la implementación para leer y escribir instancias JsonDocument
mediante JsonSerializer
es un contenedor en torno a JsonDocument.ParseValue(Utf8JsonReader) y JsonDocument.WriteTo(Utf8JsonWriter). Este contenedor no reenvía JsonSerializerOptions
(características de serializador) a Utf8JsonReader
o Utf8JsonWriter
. Por ejemplo, si establece JsonSerializerOptions.DefaultIgnoreCondition en WhenWritingNull y llama a JsonSerializer
con una sobrecarga que toma JsonSerializerOptions
, no se omiten las propiedades null.
En el ejemplo siguiente se muestra el resultado del uso de métodos que toman un parámetro JsonSerializerOptions
y serializan una instancia JsonDocument
:
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace JsonDocumentWithJsonSerializerOptions;
public class Program
{
public static void Main()
{
Person person = new Person { Name = "Nancy" };
// Default serialization - Address property included with null token.
// Output: {"Name":"Nancy","Address":null}
string personJsonWithNull = JsonSerializer.Serialize(person);
Console.WriteLine(personJsonWithNull);
// Serialize and ignore null properties - null Address property is omitted
// Output: {"Name":"Nancy"}
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string personJsonWithoutNull = JsonSerializer.Serialize(person, options);
Console.WriteLine(personJsonWithoutNull);
// Ignore null properties doesn't work when serializing JsonDocument instance
// by using JsonSerializer.
// Output: {"Name":"Nancy","Address":null}
var personJsonDocument = JsonSerializer.Deserialize<JsonDocument>(personJsonWithNull);
personJsonWithNull = JsonSerializer.Serialize(personJsonDocument, options);
Console.WriteLine(personJsonWithNull);
}
}
public class Person
{
public string? Name { get; set; }
public string? Address { get; set; }
}
Si necesita características de JsonSerializerOptions
, use JsonSerializer
con destinos fuertemente tipados (como la clase Person
de este ejemplo) en lugar de JsonDocument
.
Use Utf8JsonWriter
Utf8JsonWriter ofrece una forma de escribir texto JSON con codificación UTF-8 de alto rendimiento a partir de tipos de .NET comunes como String
, Int32
y DateTime
. El escritor es un tipo de bajo nivel que se puede usar para compilar serializadores personalizados. El método JsonSerializer.Serialize usa Utf8JsonWriter
en segundo plano.
En el siguiente ejemplo, se muestra cómo utilizar la clase Utf8JsonWriter:
var options = new JsonWriterOptions
{
Indented = true
};
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, options);
writer.WriteStartObject();
writer.WriteString("date", DateTimeOffset.UtcNow);
writer.WriteNumber("temp", 42);
writer.WriteEndObject();
writer.Flush();
string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);
Dim options As JsonWriterOptions = New JsonWriterOptions With {
.Indented = True
}
Dim stream As MemoryStream = New MemoryStream
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(stream, options)
writer.WriteStartObject()
writer.WriteString("date", DateTimeOffset.UtcNow)
writer.WriteNumber("temp", 42)
writer.WriteEndObject()
writer.Flush()
Dim json As String = Encoding.UTF8.GetString(stream.ToArray())
Console.WriteLine(json)
Escritura con texto UTF-8
Para lograr el mejor rendimiento posible mientras usa Utf8JsonWriter
, escriba cargas de JSON ya codificadas como texto UTF-8 en lugar de como cadenas UTF-16. Utilice JsonEncodedText para almacenar en caché y codificar previamente los nombres y valores de las propiedades de cadena conocidas como estáticos y pasarlos al escritor, en lugar de usar literales de cadena UTF-16. Esto es más rápido que el almacenamiento en caché y el uso de matrices de bytes UTF-8.
Este enfoque también funciona si necesita realizar un escape personalizado. System.Text.Json
no permite deshabilitar el escape mientras se escribe una cadena, pero podría pasar su propio JavaScriptEncoder personalizado como una opción al escritor, o crear su propio JsonEncodedText
que use su JavascriptEncoder
para realizar el escape y, después, escribir el JsonEncodedText
en lugar de la cadena. Para más información, vea Personalización de la codificación de caracteres.
Escritura de JSON sin formato
En algunos escenarios, es posible que quiera escribir JSON "sin formato" en una carga JSON que esté creando con Utf8JsonWriter
. Puede usar Utf8JsonWriter.WriteRawValue para ello. Estos son escenarios típicos:
Tiene una carga JSON existente que quiere incluir en nuevo JSON.
Quiere aplicar un formato diferente al predeterminado
Utf8JsonWriter
a los valores.Por ejemplo, puede que quiera personalizar el formato de número. De manera predeterminada, System.Text.Json omite el separador decimal de los números enteros, escribiendo
1
en lugar de1.0
, por ejemplo. La lógica es que escribir menos bytes es bueno para el rendimiento. Pero imagine que el consumidor de JSON trata los números con decimales como valores double y los números sin decimales como enteros. Es posible que quiera asegurarse de que todos los números de una matriz se reconozcan como valores double escribiendo un separador decimal y cero para los números enteros. En el siguiente ejemplo se muestra cómo hacerlo:using System.Text; using System.Text.Json; namespace WriteRawJson; public class Program { public static void Main() { JsonWriterOptions writerOptions = new() { Indented = true, }; using MemoryStream stream = new(); using Utf8JsonWriter writer = new(stream, writerOptions); writer.WriteStartObject(); writer.WriteStartArray("defaultJsonFormatting"); foreach (double number in new double[] { 50.4, 51 }) { writer.WriteStartObject(); writer.WritePropertyName("value"); writer.WriteNumberValue(number); writer.WriteEndObject(); } writer.WriteEndArray(); writer.WriteStartArray("customJsonFormatting"); foreach (double result in new double[] { 50.4, 51 }) { writer.WriteStartObject(); writer.WritePropertyName("value"); writer.WriteRawValue( FormatNumberValue(result), skipInputValidation: true); writer.WriteEndObject(); } writer.WriteEndArray(); writer.WriteEndObject(); writer.Flush(); string json = Encoding.UTF8.GetString(stream.ToArray()); Console.WriteLine(json); } static string FormatNumberValue(double numberValue) { return numberValue == Convert.ToInt32(numberValue) ? numberValue.ToString() + ".0" : numberValue.ToString(); } } // output: //{ // "defaultJsonFormatting": [ // { // "value": 50.4 // }, // { // "value": 51 // } // ], // "customJsonFormatting": [ // { // "value": 50.4 // }, // { // "value": 51.0 // } // ] //}
Personalización del escape de caracteres
El valor StringEscapeHandling de JsonTextWriter
ofrece opciones para escapar todos los caracteres que no sean ASCII o caracteres HTML. De forma predeterminada, Utf8JsonWriter
convierte todos los caracteres que no son ASCII y HTML. Este escape se hace por motivos de seguridad de defensa en profundidad. Para especificar una directiva de escape diferente, cree un JavaScriptEncoder y configure JsonWriterOptions.Encoder. Para más información, vea Personalización de la codificación de caracteres.
Escritura de valores NULL
Para escribir valores NULL mediante Utf8JsonWriter
, llame a:
- WriteNull para escribir un par clave-valor con NULL como valor.
- WriteNullValue para escribir NULL como un elemento de una matriz JSON.
En el caso de una propiedad de cadena, si la cadena es NULL, WriteString y WriteStringValue son equivalentes a WriteNull
y WriteNullValue
.
Escritura de valores TimeSpan, URI o char
Para escribir valores Timespan
, Uri
o char
, dé formato a estos valores como cadenas (por ejemplo, llamando a ToString()
) y llame a WriteStringValue.
Use Utf8JsonReader
Utf8JsonReader es un lector de solo avance, de baja asignación y de alto rendimiento para texto JSON con codificación UTF-8 que se lee desde ReadOnlySpan<byte>
o ReadOnlySequence<byte>
. Utf8JsonReader
es un tipo de bajo nivel que se puede usar para compilar analizadores y deserializadores personalizados. El método JsonSerializer.Deserialize usa Utf8JsonReader
en segundo plano.
Utf8JsonReader
no se puede usar directamente desde el código de Visual Basic. Para obtener más información, vea Compatibilidad con Visual Basic.
En el siguiente ejemplo, se muestra cómo utilizar la clase Utf8JsonReader:
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
En el código anterior se supone que la variable jsonUtf8
es una matriz de bytes que contiene JSON válido, con codificación UTF-8.
Filtrado de datos mediante Utf8JsonReader
En el ejemplo siguiente se muestra cómo leer un archivo de forma sincrónica y buscar un valor.
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
Puede encontrar una versión asincrónica de este ejemplo en Proyecto JSON de ejemplos de .NET.
El código anterior:
Supone que el JSON contiene una matriz de objetos y cada objeto puede contener una propiedad "name" de tipo cadena.
Cuenta los objetos y los valores de propiedad "name" que terminan en "University".
Supone que el archivo tiene codificación UTF-16 y lo transcodifica a UTF-8. Un archivo con codificación UTF-8 puede leerse directamente en
ReadOnlySpan<byte>
mediante el código siguiente:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
Si el archivo contiene una marca BOM UTF-8, quítela antes de pasar los bytes a
Utf8JsonReader
, ya que el lector espera texto. De lo contrario, la marca BOM se considera JSON no válido y el lector inicia una excepción.
Aquí se muestra un ejemplo de JSON que el código anterior puede leer. El mensaje de resumen resultante es "2 de 4 tienen nombres que terminan en '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"
}
]
Consumo de cadenas JSON descodificadas
A partir de .NET 7, puede usar el método Utf8JsonReader.CopyString en lugar de Utf8JsonReader.GetString() para consumir una cadena JSON descodificada. A diferencia de GetString(), que siempre asigna una nueva cadena, CopyString le permite copiar la cadena sin escape en un búfer de su propiedad. En el fragmento de código siguiente se muestra un ejemplo de consumo de una cadena UTF-16 mediante CopyString.
int valueLength = reader.HasReadOnlySequence
? checked((int)ValueSequence.Length)
: ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.Slice(0, charsRead);
// Handle the unescaped JSON string.
ParseUnescapedString(source);
ArrayPool<char>.Shared.Return(buffer, clearArray: true);
Lectura de una secuencia mediante Utf8JsonReader
Al leer un archivo grande (un gigabyte o más de tamaño, por ejemplo), puede que desee evitar tener que cargar todo el archivo en la memoria de una vez. En este escenario, puede usar FileStream.
Al usar Utf8JsonReader
para leer de una secuencia, se aplican las siguientes reglas:
- El búfer que contiene la carga parcial JSON debe ser al menos tan grande como el token JSON más grande que contiene para que el lector pueda avanzar.
- El búfer debe ser al menos tan grande como la secuencia más grande de espacio en blanco dentro del JSON.
- El lector no realiza un seguimiento de los datos que ha leído hasta que lea completamente el TokenType siguiente en la carga JSON. Por tanto, cuando haya bytes restantes en el búfer, tendrá que volver a pasarlos al lector. Puede usar BytesConsumed para determinar el número de bytes que quedan.
El código siguiente muestra cómo leer desde una secuencia. Este ejemplo se muestra MemoryStream. Un código similar funcionará con FileStream, excepto cuando FileStream
contenga una marca BOM UTF-8 al principio. En ese caso, debe quitar esos tres bytes del búfer antes de pasar los bytes restantes a Utf8JsonReader
. En caso contrario, el lector produciría una excepción, ya que la marca BOM no se considera una parte válida del JSON.
El código de ejemplo comienza con un búfer de 4 KB y duplica el tamaño del búfer cada vez que encuentra que el tamaño no es lo suficientemente grande como para ajustarse a un token JSON completo, lo que es necesario para que el lector realice el progreso de la carga de JSON. El ejemplo de JSON proporcionado en el fragmento de código desencadena un aumento del tamaño del búfer solo si se establece un tamaño de búfer inicial muy pequeño, por ejemplo, 10 bytes. Si establece el tamaño de búfer inicial en 10, las instrucciones Console.WriteLine
muestran la causa y el efecto de los aumentos del tamaño del búfer. En el tamaño de búfer inicial de 4 KB, se muestra todo el JSON de ejemplo en cada Console.WriteLine
y el tamaño del búfer nunca se debe aumentar.
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, 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, buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}
private static void GetMoreBytesFromStream(
MemoryStream stream, 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
En el ejemplo anterior no se establece ningún límite para el tamaño del búfer. Si el tamaño del token es demasiado grande, se podría producir un error en el código con una excepción OutOfMemoryException. Esto puede ocurrir si el archivo JSON contiene un token de aproximadamente 1 GB o más de tamaño, ya que la duplicación del tamaño de 1 GB da como resultado un tamaño demasiado grande para caber en un búfer de int32
.
Utf8JsonReader es una estructura de referencia
Dado que el tipo Utf8JsonReader
es una estructura de referencia, tiene ciertas limitaciones. Por ejemplo, no se puede almacenar como un campo en una clase o estructura que no sea una estructura de referencia. Para lograr un alto rendimiento, este tipo debe ser ref struct
, ya que necesita almacenar en caché la entrada ReadOnlySpan<byte>, que en sí misma es una estructura de referencia. Además, este tipo es mutable ya que contiene el estado; Por tanto, páselo por referencia en lugar de por valor. Si se pasa por valor, se producirá una copia de la estructura y los cambios de estado no serán visibles para el autor de la llamada. Para más información sobre el uso de las estructuras de referencia, vea Evitar asignaciones.
Lectura de texto UTF-8
Para lograr el mejor rendimiento posible mientras usa Utf8JsonReader
, lea cargas de JSON ya codificadas como texto UTF-8 en lugar de como cadenas UTF-16. Para obtener un ejemplo de código, vea Filtrado de datos mediante Utf8JsonReader.
Lectura con ReadOnlySequence de varios segmentos
Si la entrada JSON es ReadOnlySpan<byte>, se puede acceder a cada elemento JSON desde la propiedad ValueSpan
en el lector a medida que se avanza por el bucle de lectura. Pero si la entrada es ReadOnlySequence<byte> (que es el resultado de la lectura de PipeReader), algunos elementos JSON podrían ocupar varios segmentos del objeto ReadOnlySequence<byte>
. No se puede acceder a estos elementos desde ValueSpan en un bloque de memoria contiguo. En su lugar, siempre que tenga un ReadOnlySequence<byte>
de varios segmentos como entrada, sondee la propiedad HasValueSequence en el lector para averiguar cómo acceder al elemento JSON actual. Este es un patrón recomendado:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Uso de ValueTextEquals para las búsquedas de nombres de propiedad
No use ValueSpan para realizar comparaciones byte a byte mediante una llamada a SequenceEqual para las búsquedas de nombres de propiedad. En su lugar, llame a ValueTextEquals, ya que ese método anula el escape de caracteres que se van a escapar en JSON. Este es un ejemplo en el que se muestra cómo buscar una propiedad denominada "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;
}
}
Lectura de valores NULL en tipos de valor que aceptan valores NULL
Las API integradas de System.Text.Json
solo devuelven tipos de valor que no aceptan valores NULL. Por ejemplo, Utf8JsonReader.GetBoolean devuelve bool
. Si encuentra Null
en el elemento JSON, inicia una excepción. En los siguientes ejemplos se muestran dos formas de controlar valores NULL: una devolviendo un tipo de valor que acepta valores NULL y otra devolviendo el valor predeterminado:
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();
}
Vea también
- Información general de System.Text.Json
- Creación de instancias de JsonSerializerOptions
- Habilitación de la coincidencia sin distinción entre mayúsculas y minúsculas
- Personalización de los nombres y valores de propiedad
- Omisión de propiedades
- Permiso del formato JSON no válido
- Administración del desbordamiento de JSON, uso de JsonElement o JsonNode
- Conservación de las referencias y administración de las referencias circulares
- Deserialización de tipos inmutables, descriptores de acceso no públicos
- Serialización polimórfica
- Migración desde Newtonsoft.Json a System.Text.Json
- Personalización de la codificación de caracteres
- Uso de DOM, Utf8JsonReader y Utf8JsonWriter
- Escritura de convertidores personalizados para la serialización de JSON
- Compatibilidad con DateTime y DateTimeOffset
- Uso de la generación de origen
- Tipos de colecciones admitidos
- Referencia de API de System.Text.Json
- Referencia de API de System.Text.Json.Serialization