Procedimiento para realizar la migración de Newtonsoft.Json a System.Text.Json

En este artículo se muestra cómo realizar la migración de Newtonsoft.Json a System.Text.Json.

El espacio de nombres System.Text.Json proporciona funcionalidad para serializar y deserializar desde JSON (notaciones de objetos JavaScript). La biblioteca de System.Text.Json se incluye en el entorno de ejecución de .NET Core 3.1 y versiones posteriores. En cuanto a otros marcos de destino, instale el paquete NuGet System.Text.Json, que admite lo siguiente:

  • .NET Standard 2.0 y versiones posteriores
  • .NET Framework 4.7.2 y versiones posteriores
  • .NET Core 2.0, 2.1 y 2.2

System.Text.Json se centra principalmente en el rendimiento, la seguridad y el cumplimiento de estándares. Tiene algunas diferencias clave en el comportamiento predeterminado y no pretende tener paridad de características con Newtonsoft.Json. En algunos escenarios, System.Text.Json no tiene ninguna funcionalidad integrada, pero se recomiendan algunas soluciones alternativas. En otros escenarios, las soluciones alternativas no son prácticas. Si su aplicación depende de una característica que falta, puede presentar una incidencia para averiguar si se puede agregar compatibilidad para su escenario.

La mayor parte de este artículo trata sobre cómo usar la API de JsonSerializer, pero también incluye instrucciones sobre cómo usar JsonDocument (que representa los Document Object Model o DOM), Utf8JsonReader y Utf8JsonWriter.

Tabla de diferencias entre Newtonsoft.Json y System.Text.Json

En la siguiente tabla se enumeran las características de Newtonsoft.Json y las equivalentes de System.Text.Json. Existen varias categorías de equivalentes:

  • Compatible con la funcionalidad integrada. Para obtener un comportamiento similar al de System.Text.Json es posible que se requiera el uso de un atributo o una opción global.
  • No compatible; hay disponible una solución alternativa. Las soluciones alternativas son convertidores personalizados, que puede que no proporcionen una paridad completa con la funcionalidad Newtonsoft.Json. En esos casos, se proporciona un código de ejemplo como muestra. Si confía en estas características de Newtonsoft.Json, la migración requerirá modificaciones en los modelos de objetos .NET u otros cambios de código.
  • No compatible; la solución alternativa no es práctica o no es posible. Si confía en estas características de Newtonsoft.Json, no será posible realizar la migración sin cambios importantes.
Característica: Newtonsoft.Json Equivalente: System.Text.Json
Deserialización sin distinción entre mayúsculas y minúsculas de forma predeterminada ✔️ Valor global PropertyNameCaseInsensitive
Nombres de propiedad en mayúsculas y minúsculas (Camel) ✔️ Valor global PropertyNamingPolicy
Mínimo escape de caracteres ✔️ Escape de caracteres estricto, configurable
Valor global NullValueHandling.Ignore ✔️ Valor global DefaultIgnoreCondition Omitir condicionalmente una propiedad
Permitir comentarios ✔️ Valor global ReadCommentHandling
Permitir comas finales ✔️ Valor global AllowTrailingCommas
Registro del convertidor personalizado ✔️ El orden de precedencia es diferente
De forma predeterminada, no hay ninguna profundidad máxima ✔️ Profundidad máxima predeterminada de 64, configurable
Valor global PreserveReferencesHandling ✔️ Valor global ReferenceHandling
Serializar o deserializar números entre comillas ✔️ Valor global de NumberHandling, atributo [JsonNumberHandling]
Deserialización en clases y estructuras inmutables ✔️ JsonConstructor, registros C# 9
Compatibilidad con campos ✔️ Valor global IncludeFields, atributo [JsonInclude]
Valor global DefaultValueHandling ✔️ Valor global DefaultIgnoreCondition
Valor NullValueHandling en [JsonProperty] ✔️ Atributo JsonIgnore
Valor DefaultValueHandling en [JsonProperty] ✔️ Atributo JsonIgnore
Deserializar Dictionary con clave que no sea de cadena ✔️ Admitido
Compatibilidad con captadores y establecedores de propiedad no públicos ✔️ Atributo JsonInclude
Atributo [JsonConstructor] ✔️ Atributo [JsonConstructor]
Compatibilidad con una gran variedad de tipos ⚠️ Algunos tipos requieren convertidores personalizados
Serialización polimórfica ⚠️ No compatible, solución alternativa, ejemplo
Deserialización polimórfica ⚠️ No compatible, solución alternativa, ejemplo
Deserializar los tipos inferidos en propiedades de object ⚠️ No compatible, solución alternativa, ejemplo
Deserializar el literal null de JSON a tipos de valor que no aceptan valores NULL ⚠️ No compatible, solución alternativa, ejemplo
Valor Required en el atributo [JsonProperty] ⚠️ No compatible, solución alternativa, ejemplo
DefaultContractResolver para omitir las propiedades ⚠️ No compatible, solución alternativa, ejemplo
Valores DateTimeZoneHandling y DateFormatString ⚠️ No compatibles, solución alternativa, ejemplo
Devoluciones de llamada ⚠️ No compatibles, solución alternativa, ejemplo
Método JsonConvert.PopulateObject ⚠️ No compatible, solución alternativa
Valor global ObjectCreationHandling ⚠️ No compatible, solución alternativa
Agregar a colecciones sin establecedores ⚠️ No compatible, solución alternativa
Valor global ReferenceLoopHandling No compatible
Compatibilidad con atributos System.Runtime.Serialization No compatible
Valor global MissingMemberHandling No compatible
Permitir nombres de propiedad sin comillas No compatible
Permitir comillas simples alrededor de los valores de cadena No compatible
Permitir valores JSON que no son de cadena para las propiedades de cadena No compatible
Característica: Newtonsoft.Json Equivalente: System.Text.Json
Deserialización sin distinción entre mayúsculas y minúsculas de forma predeterminada ✔️ Valor global PropertyNameCaseInsensitive
Nombres de propiedad en mayúsculas y minúsculas (Camel) ✔️ Valor global PropertyNamingPolicy
Mínimo escape de caracteres ✔️ Escape de caracteres estricto, configurable
Valor global NullValueHandling.Ignore ✔️ Opción global IgnoreNullValues
Permitir comentarios ✔️ Valor global ReadCommentHandling
Permitir comas finales ✔️ Valor global AllowTrailingCommas
Registro del convertidor personalizado ✔️ El orden de precedencia es diferente
De forma predeterminada, no hay ninguna profundidad máxima ✔️ Profundidad máxima predeterminada de 64, configurable
Compatibilidad con una gran variedad de tipos ⚠️ Algunos tipos requieren convertidores personalizados
Deserializar cadenas como números ⚠️ No compatible, solución alternativa, ejemplo
Deserializar Dictionary con clave que no sea de cadena ⚠️ No compatible, solución alternativa, ejemplo
Serialización polimórfica ⚠️ No compatible, solución alternativa, ejemplo
Deserialización polimórfica ⚠️ No compatible, solución alternativa, ejemplo
Deserializar los tipos inferidos en propiedades de object ⚠️ No compatible, solución alternativa, ejemplo
Deserializar el literal null de JSON a tipos de valor que no aceptan valores NULL ⚠️ No compatible, solución alternativa, ejemplo
Deserialización en clases y estructuras inmutables ⚠️ No compatible, solución alternativa, ejemplo
Atributo [JsonConstructor] ⚠️ No compatible, solución alternativa, ejemplo
Valor Required en el atributo [JsonProperty] ⚠️ No compatible, solución alternativa, ejemplo
Valor NullValueHandling en el atributo [JsonProperty] ⚠️ No compatible, solución alternativa, ejemplo
Valor DefaultValueHandling en el atributo [JsonProperty] ⚠️ No compatible, solución alternativa, ejemplo
Valor global DefaultValueHandling ⚠️ No compatible, solución alternativa, ejemplo
DefaultContractResolver para omitir las propiedades ⚠️ No compatible, solución alternativa, ejemplo
Valores DateTimeZoneHandling y DateFormatString ⚠️ No compatibles, solución alternativa, ejemplo
Devoluciones de llamada ⚠️ No compatibles, solución alternativa, ejemplo
Compatibilidad con campos públicos y no públicos ⚠️ No compatible, solución alternativa
Compatibilidad con captadores y establecedores de propiedad no públicos ⚠️ No compatible, solución alternativa
Método JsonConvert.PopulateObject ⚠️ No compatible, solución alternativa
Valor global ObjectCreationHandling ⚠️ No compatible, solución alternativa
Agregar a colecciones sin establecedores ⚠️ No compatible, solución alternativa
Valor global PreserveReferencesHandling No compatible
Valor global ReferenceLoopHandling No compatible
Compatibilidad con atributos System.Runtime.Serialization No compatible
Valor global MissingMemberHandling No compatible
Permitir nombres de propiedad sin comillas No compatible
Permitir comillas simples alrededor de los valores de cadena No compatible
Permitir valores JSON que no son de cadena para las propiedades de cadena No compatible

Esta no es una lista exhaustiva de características de Newtonsoft.Json. La lista incluye muchos de los escenarios que se han solicitado en publicaciones de problemas de GitHub o StackOverflow. Si implementa una solución alternativa para uno de los escenarios que aquí se enumeran que no tenga actualmente un código de ejemplo, y si quiere compartir la solución, haga clic en Esta página en la sección Comentarios de la parte inferior de esta página. De esta forma se abre una incidencia en el repositorio de GitHub de esta documentación y también se muestra en la sección Comentarios de esta página.

Diferencias en el comportamiento predeterminado de JsonSerializer en comparación con Newtonsoft.Json

System.Text.Json es estricto de forma predeterminada y evita cualquier conjetura o interpretación en nombre del llamador, con lo que resalta el comportamiento determinista. La biblioteca se ha diseñado intencionadamente de esta manera por motivos de rendimiento y seguridad. De manera predeterminada, Newtonsoft.Json es flexible. Esta diferencia fundamental en el diseño es la responsable de muchas de las siguientes diferencias específicas en el comportamiento predeterminado.

Deserialización sin distinción entre mayúsculas y minúsculas

Durante la deserialización, Newtonsoft.Json realiza de forma predeterminada la coincidencia de nombres de propiedad sin distinción entre mayúsculas y minúsculas. El valor predeterminado de System.Text.Json distingue entre mayúsculas y minúsculas, lo que proporciona un mejor rendimiento, ya que realiza una coincidencia exacta. Para obtener información sobre cómo realizar la coincidencia sin distinción entre mayúsculas y minúsculas, vea Coincidencia de propiedades sin distinción entre mayúsculas y minúsculas.

Si usa System.Text.Json indirectamente mediante ASP.NET Core, no es necesario hacer nada para obtener un comportamiento como Newtonsoft.Json. ASP.NET Core especifica los valores para los nombres de propiedad con grafía Camel y la coincidencia sin distinción entre mayúsculas y minúsculas cuando usa System.Text.Json.

ASP.NET Core también permite deserializar números entrecomillados de forma predeterminada.

Mínimo escape de caracteres

Durante la serialización, Newtonsoft.Json es relativamente permisivo con respecto a si se permite que los caracteres queden sin escapar. Es decir, no los reemplaza por \uxxxx donde xxxx es el punto de código del carácter. En aquellos casos donde los escapa, lo hace emitiendo una \ antes del carácter (por ejemplo, " se convierte en \"). De forma predeterminada, System.Text.Json escapa más caracteres para proporcionar protecciones de defensa en profundidad contra los ataques de scripting entre sitios (XSS) o de divulgación de información, y lo hace mediante el uso de la secuencia de seis caracteres. System.Text.Json escapa todos los caracteres que no sean ASCII de forma predeterminada, por lo que no es necesario hacer nada si usa StringEscapeHandling.EscapeNonAscii en Newtonsoft.Json. System.Text.Json de forma predeterminada también escapa los caracteres que distinguen HTML. Para obtener información sobre cómo invalidar el comportamiento predeterminado de System.Text.Json, vea Personalización de la codificación de caracteres.

Comentarios

Durante la deserialización, Newtonsoft.Json omite de forma predeterminada los comentarios en JSON. El valor predeterminado de System.Text.Json es producir excepciones para los comentarios porque la especificación RFC 8259 no los incluye. Para obtener información sobre cómo permitir comentarios, vea Permitir comentarios y comas finales.

Comas finales

Durante la deserialización, Newtonsoft.Json omite de forma predeterminada las comas finales. También omite varias comas finales (por ejemplo, [{"Color":"Red"},{"Color":"Green"},,]). El valor predeterminado de System.Text.Json es producir excepciones para las comas finales porque la especificación RFC 8259 no las permite. Para obtener información sobre cómo hacer que System.Text.Json las acepte, vea Permitir comentarios y comas finales. No hay ninguna manera de permitir varias comas finales.

Precedencia de registro del convertidor

La precedencia de registro de Newtonsoft.Json para los convertidores personalizados es la siguiente:

  • Atributo en la propiedad
  • Atributo en el tipo
  • Colección de convertidores

Este orden implica que un convertidor que se registre aplicando un atributo en el nivel de tipo invalidará a un convertidor personalizado de la colección Converters, y un atributo en el nivel de propiedad invalidará a ambos registros.

La precedencia de registro de System.Text.Json para los convertidores personalizados es diferente:

  • Atributo en la propiedad
  • Colección Converters
  • Atributo en el tipo

En este caso, la diferencia es que un convertidor personalizado de la colección Converters invalida a un atributo en el nivel de tipo. La intención de este orden de precedencia es que los cambios de tiempo de ejecución invaliden las opciones de tiempo de diseño. No hay ninguna manera de cambiar la precedencia.

Para más información sobre el registro de convertidores personalizados, vea Registro de un convertidor personalizado.

Profundidad máxima

De forma predeterminada, Newtonsoft.Json no tiene un límite de profundidad máxima. Par System.Text.Json hay un límite predeterminado de 64, y se puede configurar mediante el valor JsonSerializerOptions.MaxDepth.

Si usa System.Text.Json indirectamente mediante ASP.NET Core, el límite predeterminado de profundidad máxima es de 32. El valor predeterminado es el mismo que para el enlace de modelos y se establece en la clase JsonOptions.

Cadenas JSON (nombres de propiedad y valores de cadena)

Durante la deserialización, Newtonsoft.Json acepta nombres de propiedad entre comillas dobles, comillas simples o sin comillas. Acepta valores de cadena entre comillas dobles o comillas simples. Por ejemplo, Newtonsoft.Json acepta el siguiente código JSON:

{
  "name1": "value",
  'name2': "value",
  name3: 'value'
}

System.Text.Json solo acepta nombres de propiedad y valores de cadena entre comillas dobles, ya que ese es el formato requerido por la especificación RFC 8259 y es el único formato que se considera JSON válido.

Un valor entre comillas simples da como resultado una JsonException con el siguiente mensaje:

''' is an invalid start of a value.

Valores que no son de cadena para propiedades de cadena

Newtonsoft.Json acepta valores que no son de cadena, como un número o los literales true y false para la deserialización de las propiedades de tipo cadena. A continuación se muestra un ejemplo de JSON que deserializa correctamente Newtonsoft.Json en la clase siguiente:

{
  "String1": 1,
  "String2": true,
  "String3": false
}
public class ExampleClass
{
    public string String1 { get; set; }
    public string String2 { get; set; }
    public string String3 { get; set; }
}

System.Text.Json no deserializa valores que no son de cadena en propiedades de cadena. Un valor que no sea de cadena recibido para un campo de cadena da como resultado una JsonException con el siguiente mensaje:

The JSON value could not be converted to System.String.

Escenarios con JsonSerializer

Algunos de los escenarios siguientes no son compatibles con la funcionalidad integrada, pero hay disponibles soluciones alternativas. Las soluciones alternativas son convertidores personalizados, que puede que no proporcionen una paridad completa con la funcionalidad Newtonsoft.Json. En esos casos, se proporciona un código de ejemplo como muestra. Si confía en estas características de Newtonsoft.Json, la migración requerirá modificaciones en los modelos de objetos .NET u otros cambios de código.

Para algunos de los escenarios siguientes, no hay ninguna solución alternativa que sea práctica o posible. Si confía en estas características de Newtonsoft.Json, no será posible realizar la migración sin cambios importantes.

Permitir o escribir números entre comillas

Newtonsoft.Json puede serializar o deserializar los números representados por cadenas JSON (entre comillas). Por ejemplo, puede aceptar: {"DegreesCelsius":"23"} en lugar de {"DegreesCelsius":23}. Para habilitar ese comportamiento en System.Text.Json, establezca JsonSerializerOptions.NumberHandling en WriteAsString o AllowReadingFromString, o use el atributo [JsonNumberHandling].

Si usa System.Text.Json indirectamente mediante ASP.NET Core, no es necesario hacer nada para obtener un comportamiento como Newtonsoft.Json. ASP.NET Core especifica valores predeterminados web cuando utiliza System.Text.Json y los valores predeterminados web permiten números entre comillas.

Para obtener más información, vea Permitir o escribir números entre comillas.

Newtonsoft.Json puede serializar o deserializar los números representados por cadenas JSON (entre comillas). Por ejemplo, puede aceptar: {"DegreesCelsius":"23"} en lugar de {"DegreesCelsius":23}. Para habilitar ese comportamiento en System.Text.Json en .NET Core 3.1, implemente un convertidor personalizado como el ejemplo siguiente. El convertidor controla las propiedades definidas como long:

  • Las serializa como cadenas JSON.
  • Acepta números de JSON y números entre comillas durante la deserialización.
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class LongToStringConverter : JsonConverter<long>
    {
        public override long Read(
            ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                ReadOnlySpan<byte> span =
                    reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;

                if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) &&
                    span.Length == bytesConsumed)
                {
                    return number;
                }

                if (long.TryParse(reader.GetString(), out number))
                {
                    return number;
                }
            }

            return reader.GetInt64();
        }

        public override void Write(
            Utf8JsonWriter writer, long longValue, JsonSerializerOptions options) =>
            writer.WriteStringValue(longValue.ToString());
    }
}

Registre este convertidor personalizado usando un atributo en propiedades individuales de long o agregando el convertidor a la colección Converters.

Especificación del constructor que se va a usar al deserializar

El atributo [JsonConstructor] de Newtonsoft.Json le permite especificar el constructor al que se llamará al deserializar en un objeto POCO.

System.Text.Json también tiene un atributo [JsonConstructor]. Para obtener más información, vea Tipos y registros inmutables.

System.Text.Json en .NET Core 3.1 solo admite constructores carentes de parámetros. Como solución alternativa, puede llamar al constructor que necesite en un convertidor personalizado. Vea el ejemplo para deserialización en clases y estructuras inmutables.

Omitir condicionalmente una propiedad

Newtonsoft.Json ofrece varias formas de omitir condicionalmente una propiedad en la serialización o deserialización:

  • DefaultContractResolver le permite seleccionar las propiedades que se van a incluir u omitir, en función de criterios arbitrarios.
  • Los valores NullValueHandling y DefaultValueHandling de JsonSerializerSettings le permiten especificar que se deben omitir todas las propiedades de valores NULL o valores predeterminados.
  • Los valores NullValueHandling y DefaultValueHandling del atributo [JsonProperty] le permiten especificar propiedades individuales que se deben omitir cuando se establecen en null o en el valor predeterminado.

System.Text.Json proporciona las siguientes formas de omitir propiedades o campos durante la serialización:

System.Text.Json en .NET Core 3.1 proporciona las siguientes formas de omitir las propiedades durante la serialización:

  • El atributo [JsonIgnore] de una propiedad hace que la propiedad se omita en el objeto JSON durante la serialización.
  • La opción global IgnoreNullValues le permite omitir todas las propiedades de valores NULL.
  • La opción global IgnoreReadOnlyProperties le permite omitir todas las propiedades de solo lectura.

Estas opciones no le permiten:

  • Omitir las propiedades seleccionadas en función de criterios arbitrarios evaluados en tiempo de ejecución.
  • Omitir todas las propiedades que tienen el valor predeterminado para el tipo.
  • Omitir todas las propiedades seleccionadas que tienen el valor predeterminado para el tipo.
  • Omitir las propiedades seleccionadas si su valor es NULL.
  • Omitir las propiedades seleccionadas en función de criterios arbitrarios evaluados en tiempo de ejecución.

Para esa funcionalidad, puede escribir un convertidor personalizado. Este es un POCO de ejemplo y un convertidor personalizado para él que ilustra este enfoque:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRuntimeIgnoreConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            var wf = new WeatherForecast();

            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return wf;
                }

                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    string propertyName = reader.GetString();
                    reader.Read();
                    switch (propertyName)
                    {
                        case "Date":
                            DateTimeOffset date = reader.GetDateTimeOffset();
                            wf.Date = date;
                            break;
                        case "TemperatureCelsius":
                            int temperatureCelsius = reader.GetInt32();
                            wf.TemperatureCelsius = temperatureCelsius;
                            break;
                        case "Summary":
                            string summary = reader.GetString();
                            wf.Summary = string.IsNullOrWhiteSpace(summary) ? "N/A" : summary;
                            break;
                    }
                }
            }

            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, WeatherForecast wf, JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            writer.WriteString("Date", wf.Date);
            writer.WriteNumber("TemperatureCelsius", wf.TemperatureCelsius);
            if (!string.IsNullOrWhiteSpace(wf.Summary) && wf.Summary != "N/A")
            {
                writer.WriteString("Summary", wf.Summary);
            }

            writer.WriteEndObject();
        }
    }
}

El convertidor hace que se omita la propiedad Summary de la serialización si su valor es NULL, una cadena vacía o "N/A".

Registre este convertidor personalizado usando un atributo en la clase o agregando el convertidor a la colección Converters.

Este enfoque requiere lógica adicional en los siguientes casos:

  • El objeto POCO incluye propiedades complejas.
  • Debe controlar los atributos como [JsonIgnore], o las opciones como los codificadores personalizados.

Campos públicos y no públicos

Newtonsoft.Json puede serializar y deserializar los campos, así como las propiedades.

En System.Text.Json, use el valor global JsonSerializerOptions.IncludeFields o el atributo [JsonInclude] para incluir los campos públicos al serializar o deserializar. Para obtener un ejemplo, vea Inclusión de campos.

System.Text.Json en .NET Core 3.1 solo funciona con propiedades públicas. Los convertidores personalizados no proporcionan esta funcionalidad.

Conservación de referencias a objetos y procesado de bucles

De forma predeterminada, Newtonsoft.Json realiza la serialización por valor. Por ejemplo, si un objeto contiene dos propiedades que contienen una referencia al mismo objeto Person, los valores de las propiedades de dicho objeto Person se duplican en JSON.

Newtonsoft.Json tiene un valor PreserveReferencesHandling en JsonSerializerSettings que le permite realizar serializaciones por referencia:

  • Los metadatos de un identificador se agregan al JSON creado para el primer objeto Person.
  • El JSON que se crea para el segundo objeto Person contiene una referencia a ese identificador en lugar de los valores de propiedad.

Newtonsoft.Json también tiene un valor ReferenceLoopHandling que le permite omitir las referencias circulares en lugar de producir una excepción.

Para conservar las referencias y administrar las referencias circulares en System.Text.Json, establezca JsonSerializerOptions.ReferenceHandler en Preserve. El valor ReferenceHandler.Preserve es equivalente a PreserveReferencesHandling = PreserveReferencesHandling.All en Newtonsoft.Json.

Al igual que Newtonsoft.Json ReferenceResolver, la clase System.Text.Json.Serialization.ReferenceResolver define el comportamiento de conservar las referencias en la serialización y deserialización. Cree una clase derivada para especificar el comportamiento personalizado. Para obtener un ejemplo, vea GuidReferenceResolver.

No se admiten algunas características Newtonsoft.Json relacionadas:

Para más información, consulte Conservación de las referencias y administración de las referencias circulares.

System.Text.Json en .NET Core 3.1 solo admite la serialización por valor y produce una excepción para las referencias circulares.

Diccionario con clave que no es de cadena

Tanto Newtonsoft.Json como System.Text.Json admiten colecciones de tipo Dictionary<TKey, TValue>.

Newtonsoft.Json admite colecciones de tipo Dictionary<TKey, TValue>. La compatibilidad integrada con colecciones de diccionarios en System.Text.Json en .NET Core 3.1 se limita a Dictionary<string, TValue>. Por lo tanto, la clave debe ser una cadena.

Para admitir un diccionario con un entero o algún otro tipo como clave en .NET Core 3.1, cree un convertidor como el ejemplo de Procedimientos para escribir convertidores personalizados.

Tipos sin compatibilidad integrada

System.Text.Json no proporciona compatibilidad integrada con los siguientes tipos:

Se pueden implementar convertidores personalizados para tipos que no tienen compatibilidad integrada.

Serialización polimórfica

Newtonsoft.Json realiza automáticamente la serialización polimórfica. Para obtener información sobre las capacidades limitadas de serialización polimórfica de System.Text.Json, vea Serialización de propiedades de clases derivadas.

La solución alternativa que se describe aquí es para definir propiedades que pueden contener clases derivadas como el tipo object. Si eso no es posible, otra opción es crear un convertidor con un método Write para toda la jerarquía de tipo de herencia, como en el ejemplo de Cómo escribir convertidores personalizados.

Deserialización polimórfica

Newtonsoft.Json tiene un valor TypeNameHandling que agrega metadatos de nombre de tipo al JSON durante la serialización. Usa los metadatos durante la deserialización para realizar la deserialización polimórfica. System.Text.Json puede realizar un intervalo limitado de serialización polimórfica, pero no deserialización polimórfica.

Para admitir la deserialización polimórfica, cree un convertidor como el ejemplo de Cómo escribir convertidores personalizados.

Deserialización de propiedades de objeto

Cuando Newtonsoft.Json deserializa en Object:

  • infiere el tipo de valores primitivos en la carga JSON (excepto null) y devuelve los valores string, long, double, boolean o DateTime almacenados como un objeto al que se ha aplicado la conversión boxing. Los valores primitivos son valores JSON únicos, como un número JSON, una cadena, un valor true, false o null.
  • Devuelve JObject o JArray para valores complejos en la carga de JSON. Los valores complejos son colecciones de pares clave-valor JSON entre llaves ({}) o listas de valores entre corchetes ([]). Las propiedades y los valores entre llaves o corchetes pueden tener propiedades o valores adicionales.
  • Devuelve una referencia nula cuando la carga útil tiene el literal JSON null.

System.Text.Json almacena un objeto JsonElement al que se ha aplicado la conversión boxing para valores primitivos y los complejos, siempre que se deserialice en Object; por ejemplo:

  • Propiedad object.
  • Un valor de diccionario object.
  • Un valor de matriz object.
  • Una raíz object.

Pero System.Text.Json trata null igual que Newtonsoft.Json, y devuelve una referencia nula cuando la carga útil tiene el literal JSON null en ella.

Para implementar la inferencia de tipos para las propiedades object, cree un convertidor como el ejemplo de Cómo escribir convertidores personalizados.

Deserialización de null en un tipo que no acepta valores NULL

Newtonsoft.Json no provoca una excepción en el escenario siguiente:

  • NullValueHandling se establece en Ignore y,
  • durante la deserialización, el archivo JSON contiene un valor NULL para un tipo de valor que no acepta valores NULL.

En el mismo escenario, System.Text.Json produce una excepción. (La configuración de control de valores NULL correspondiente en System.Text.Json es JsonSerializerOptions.IgnoreNullValues = true).

Si es el propietario del tipo de destino, la mejor solución alternativa posible es hacer que la propiedad en cuestión acepte valores NULL (por ejemplo, cambiar int a int?).

Otra solución alternativa consiste en crear un convertidor para el tipo, como en el ejemplo siguiente, en el que se tratan los valores NULL de los tipos DateTimeOffset:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>
    {
        public override DateTimeOffset Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
            reader.TokenType == JsonTokenType.Null
                ? default
                : reader.GetDateTimeOffset();

        public override void Write(
            Utf8JsonWriter writer,
            DateTimeOffset dateTimeValue,
            JsonSerializerOptions options) =>
            writer.WriteStringValue(dateTimeValue);
    }
}

Registre este convertidor personalizado usando un atributo en la propiedad o agregando el convertidor a la colección Converters.

Nota: El convertidor anterior trata los valores NULL de manera diferente de como lo hace Newtonsoft.Json para los POCO que especifican valores predeterminados. Por ejemplo, suponga que el siguiente código representa su objeto de destino:

public class WeatherForecastWithDefault
{
    public WeatherForecastWithDefault()
    {
        Date = DateTimeOffset.Parse("2001-01-01");
        Summary = "No summary";
    }
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

Y suponga que el siguiente código JSON se deserializa con el convertidor anterior:

{
  "Date": null,
  "TemperatureCelsius": 25,
  "Summary": null
}

Después de la deserialización, la propiedad Date tiene 1/1/0001 (default(DateTimeOffset)), es decir, se sobrescribe el valor establecido en el constructor. Dados los mismos objetos POCO y JSON, la deserialización de Newtonsoft.Json dejaría 1/1/2001 en la propiedad Date.

Deserialización en clases y estructuras inmutables

Newtonsoft.Json se puede deserializar en clases y estructuras inmutables, ya que puede usar constructores que tengan parámetros.

En System.Text.Json, use el atributo [JsonConstructor] para especificar el uso de un constructor con parámetros. Los registros en C# 9 también son inmutables y se admiten como destinos de deserialización. Para obtener más información, vea Tipos y registros inmutables.

System.Text.Json en .NET Core 3.1 solo admite constructores públicos carentes de parámetros. Como solución alternativa, puede llamar a un constructor con parámetros en un convertidor personalizado.

A continuación se muestra una estructura inmutable con varios parámetros de constructor:

public readonly struct ImmutablePoint
{
    public ImmutablePoint(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; }
    public int Y { get; }
}

Y este es un convertidor que serializa y deserializa esta estructura:

using System;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
    {
        private readonly JsonEncodedText _xName = JsonEncodedText.Encode("X");
        private readonly JsonEncodedText _yName = JsonEncodedText.Encode("Y");

        private readonly JsonConverter<int> _intConverter;

        public ImmutablePointConverter(JsonSerializerOptions options) => 
            _intConverter = options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter
                ? intConverter
                : throw new InvalidOperationException();

        public override ImmutablePoint Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            };

            int? x = default;
            int? y = default;

            // Get the first property.
            reader.Read();
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            if (reader.ValueTextEquals(_xName.EncodedUtf8Bytes))
            {
                x = ReadProperty(ref reader, options);
            }
            else if (reader.ValueTextEquals(_yName.EncodedUtf8Bytes))
            {
                y = ReadProperty(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }

            // Get the second property.
            reader.Read();
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            if (x.HasValue && reader.ValueTextEquals(_yName.EncodedUtf8Bytes))
            {
                y = ReadProperty(ref reader, options);
            }
            else if (y.HasValue && reader.ValueTextEquals(_xName.EncodedUtf8Bytes))
            {
                x = ReadProperty(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }

            reader.Read();

            if (reader.TokenType != JsonTokenType.EndObject)
            {
                throw new JsonException();
            }

            return new ImmutablePoint(x.GetValueOrDefault(), y.GetValueOrDefault());
        }

        private int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
        {
            Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);

            reader.Read();
            return _intConverter.Read(ref reader, typeof(int), options);
        }

        private void WriteProperty(Utf8JsonWriter writer, JsonEncodedText name, int intValue, JsonSerializerOptions options)
        {
            writer.WritePropertyName(name);
            _intConverter.Write(writer, intValue, options);
        }

        public override void Write(
            Utf8JsonWriter writer,
            ImmutablePoint point,
            JsonSerializerOptions options)
        {
            writer.WriteStartObject();
            WriteProperty(writer, _xName, point.X, options);
            WriteProperty(writer, _yName, point.Y, options);
            writer.WriteEndObject();
        }
    }
}

Registre este convertidor personalizado agregando el convertidor a la colección Converters.

Para obtener un ejemplo de un convertidor similar que controla las propiedades genéricas abiertas, vea el convertidor integrado para pares clave-valor.

Propiedades obligatorias

En Newtonsoft.Json, especifica que se requiere una propiedad estableciendo Required en el atributo [JsonProperty]. Newtonsoft.Json produce una excepción si no se recibe ningún valor en el objeto JSON para una propiedad marcada como requerida.

System.Text.Json no produce una excepción si no se recibe ningún valor para una de las propiedades del tipo de destino. Por ejemplo, si tiene una clase WeatherForecast:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

El siguiente JSON se deserializa sin errores:

{
    "TemperatureCelsius": 25,
    "Summary": "Hot"
}

Para que se produzca un error en la deserialización si no hay una propiedad Date en el objeto JSON, implemente un convertidor personalizado. El siguiente código de convertidor de ejemplo produce una excepción si no se establece la propiedad Date cuando se completa la deserialización:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRequiredPropertyConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // Don't pass in options when recursively calling Deserialize.
            WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

            // Check for required fields set by values in JSON
            return forecast.Date == default
                ? throw new JsonException("Required property not received in the JSON")
                : forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecast forecast, JsonSerializerOptions options)
        {
            // Don't pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(writer, forecast);
        }
    }
}

Registre este convertidor personalizado agregando el convertidor a la colección JsonSerializerOptions.Converters.

Este patrón de llamada recursiva al convertidor exige que se registre el convertidor mediante JsonSerializerOptions, no mediante un atributo. Si registra el convertidor mediante un atributo, el convertidor personalizado se llama a sí mismo de forma recursiva. El resultado es un bucle infinito que finaliza en una excepción de desbordamiento de pila.

Al registrar el convertidor mediante el objeto de opciones, evite un bucle infinito; para ello, no pase el objeto de opciones cuando llame a Serialize o Deserialize de forma recursiva. El objeto de opciones contiene la colección Converters. Si lo pasa a Serialize o Deserialize, el convertidor personalizado se llama a sí mismo, con lo que se crea un bucle infinito que produce una excepción de desbordamiento de pila. Si las opciones predeterminadas no son factibles, cree una nueva instancia de las opciones con la configuración que necesite. Este enfoque será lento, ya que cada instancia nueva se almacena en caché de forma independiente.

Existe un patrón alternativo que puede usar el registro de JsonConverterAttribute en la clase que se va a convertir. En este enfoque, el código del convertidor llama a Serialize o Deserialize en una clase que deriva de la clase que se va a convertir. La clase derivada no tiene ningún elemento JsonConverterAttribute aplicado. En el siguiente ejemplo de esta alternativa:

  • WeatherForecastWithRequiredPropertyConverterAttribute es la clase que se va a deserializar y a la que se le ha aplicado JsonConverterAttribute.
  • WeatherForecastWithoutRequiredPropertyConverterAttribute es la clase derivada que no tiene el atributo del convertidor.
  • El código del convertidor llama a Serialize y Deserialize en WeatherForecastWithoutRequiredPropertyConverterAttribute para evitar un bucle infinito. Hay un costo de rendimiento en este enfoque aplicado a la serialización debido a una creación de instancias de objeto adicional y la copia de valores de propiedad.

Estos son los tipos WeatherForecast*:

[JsonConverter(typeof(WeatherForecastRequiredPropertyConverterForAttributeRegistration))]
public class WeatherForecastWithRequiredPropertyConverterAttribute
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

public class WeatherForecastWithoutRequiredPropertyConverterAttribute :
    WeatherForecastWithRequiredPropertyConverterAttribute
{
}

Y este es el convertidor:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRequiredPropertyConverterForAttributeRegistration :
        JsonConverter<WeatherForecastWithRequiredPropertyConverterAttribute>
    {
        public override WeatherForecastWithRequiredPropertyConverterAttribute Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // OK to pass in options when recursively calling Deserialize.
            WeatherForecastWithRequiredPropertyConverterAttribute forecast =
                JsonSerializer.Deserialize<WeatherForecastWithoutRequiredPropertyConverterAttribute>(
                    ref reader,
                    options);

            // Check for required fields set by values in JSON.
            return forecast.Date == default
                ? throw new JsonException("Required property not received in the JSON")
                : forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecastWithRequiredPropertyConverterAttribute forecast,
            JsonSerializerOptions options)
        {
            var weatherForecastWithoutConverterAttributeOnClass =
                new WeatherForecastWithoutRequiredPropertyConverterAttribute
                {
                    Date = forecast.Date,
                    TemperatureCelsius = forecast.TemperatureCelsius,
                    Summary = forecast.Summary
                };

            // OK to pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(
                writer,
                weatherForecastWithoutConverterAttributeOnClass,
                options);
        }
    }
}

El convertidor de propiedades necesario requeriría lógica adicional en el caso de necesitar administrar atributos como [JsonIgnore] u otras opciones, como codificadores personalizados. Además, el código de ejemplo no controla las propiedades para las que se establece un valor predeterminado en el constructor, y este enfoque no distingue entre los siguientes escenarios:

  • Falta una propiedad en el objeto JSON.
  • Hay una propiedad para un tipo que no acepta valores NULL en el objeto JSON, pero el valor es el predeterminado para el tipo, como cero para int.
  • Hay una propiedad para un tipo de valor que acepta valores NULL en el objeto JSON, pero el valor es NULL.

Especificación del formato de fecha

Newtonsoft.Json proporciona varias maneras de controlar cómo se serializan y deserializan las propiedades de los tipos DateTime y DateTimeOffset:

  • El valor DateTimeZoneHandling se puede usar para serializar todos los valores DateTime como fechas UTC.
  • El valor DateFormatString y los convertidores de DateTime se pueden usar para personalizar el formato de las cadenas de fecha.

System.Text.Json admite ISO 8601-1:2019, incluido el perfil RFC 3339. Este formato está ampliamente adoptado, no es ambiguo, y hace que los recorridos de ida y vuelta se realicen con precisión. Para usar cualquier otro formato, cree un convertidor personalizado. Para obtener más información, consulte Compatibilidad con DateTime y DateTimeOffset en System.Text.Json.

Devoluciones de llamada

Newtonsoft.Json le permite ejecutar código personalizado en varios puntos en el proceso de serialización o deserialización:

  • OnDeserializing: al empezar a deserializar un objeto
  • OnDeserialized: al finalizar la deserialización de un objeto
  • OnSerializing: al empezar a serializar un objeto
  • OnSerialized: al finalizar la serialización de un objeto

En System.Text.Json, puede simular devoluciones de llamada escribiendo un convertidor personalizado. En el ejemplo siguiente se muestra un convertidor personalizado para un objeto POCO. El convertidor incluye código que muestra un mensaje en cada punto que corresponde a una devolución de llamada de Newtonsoft.Json.

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastCallbacksConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // Place "before" code here (OnDeserializing),
            // but note that there is no access here to the POCO instance.
            Console.WriteLine("OnDeserializing");

            // Don't pass in options when recursively calling Deserialize.
            WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

            // Place "after" code here (OnDeserialized)
            Console.WriteLine("OnDeserialized");

            return forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecast forecast, JsonSerializerOptions options)
        {
            // Place "before" code here (OnSerializing)
            Console.WriteLine("OnSerializing");

            // Don't pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(writer, forecast);

            // Place "after" code here (OnSerialized)
            Console.WriteLine("OnSerialized");
        }
    }
}

Registre este convertidor personalizado agregando el convertidor a la colección Converters.

Si usa un convertidor personalizado que sigue el ejemplo anterior:

  • El código OnDeserializing no tiene acceso a la nueva instancia POCO. Para manipular la nueva instancia POCO al inicio de la deserialización, coloque ese código en el constructor POCO.
  • Evite un bucle infinito; para ello, registre el convertidor en el objeto de opciones y no pase el objeto de opciones cuando llame a Serialize o Deserialize de forma recursiva.

Para más información sobre los convertidores personalizados que llaman a Serialize o Deserialize de forma recursiva, consulte la sección Propiedades obligatorias anteriormente en este artículo.

Captadores y establecedores de propiedad no públicos

Newtonsoft.Json puede usar captadores y establecedores de propiedades internos y privados a través del atributo JsonProperty.

System.Text.Json admite captadores y establecedores de propiedades internos y privados a través del atributo [JsonInclude]. Para obtener código de ejemplo, vea Descriptores de acceso de propiedad no públicos.

System.Text.Json en .NET Core 3.1 solo admite establecedores públicos. Los convertidores personalizados no proporcionan esta funcionalidad.

Rellenar objetos existentes

El método JsonConvert.PopulateObject de Newtonsoft.Json deserializa un documento JSON en una instancia existente de una clase, en lugar de crear una nueva instancia. System.Text.Json siempre crea una nueva instancia del tipo de destino mediante el constructor sin parámetros público predeterminado. Los convertidores personalizados se pueden deserializar en una instancia existente.

Reutilización en lugar de reemplazo de propiedades

El valor ObjectCreationHandling de Newtonsoft.Json le permite especificar que los objetos de las propiedades deben reutilizarse en lugar de reemplazarse durante la deserialización. System.Text.Json siempre reemplaza los objetos en las propiedades. Los convertidores personalizados no proporcionan esta funcionalidad.

Agregar a colecciones sin establecedores

Durante la deserialización, Newtonsoft.Json agrega objetos a una colección, incluso si la propiedad no tiene ningún establecedor. System.Text.Json omite las propiedades que no tienen establecedores. Los convertidores personalizados no proporcionan esta funcionalidad.

Atributos de System.Runtime.Serialization

System.Text.Json no admite atributos del espacio de nombres System.Runtime.Serialization, como DataMemberAttribute y IgnoreDataMemberAttribute.

Números octales

Newtonsoft.Json trata los números con un cero a la izquierda como números octales. System.Text.Json no permite ceros a la izquierda porque la especificación RFC 8259 no los permite.

MissingMemberHandling

Newtonsoft.Json se puede configurar para producir excepciones durante la deserialización si el archivo JSON incluye propiedades que faltan en el tipo de destino. System.Text.Json omite las propiedades adicionales en el archivo JSON, excepto cuando se usa el atributo [JsonExtensionData]. No hay ninguna solución alternativa para la característica de miembro que falta.

TraceWriter

Newtonsoft.Json le permite realizar la depuración mediante el uso de un TraceWriter para ver los registros generados por la serialización o deserialización. System.Text.Json no realiza el registro.

JsonDocument y JsonElement en comparación con JToken (como JObject, JArray)

System.Text.Json.JsonDocument proporciona la capacidad de analizar y compilar un Document Object Model (DOM) de solo lectura a partir de cargas JSON existentes. DOM proporciona acceso aleatorio a los datos en una carga JSON. A los elementos JSON que componen los datos se puede acceder mediante el tipo JsonElement. El tipo JsonElement proporciona las API para convertir texto JSON en tipos comunes de .NET. JsonDocument expone una propiedad RootElement.

JsonDocument es IDisposable

JsonDocument compila una vista en memoria de los datos en un búfer agrupado. Por lo tanto, a diferencia de JObject o JArray de Newtonsoft.Json, 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 es de solo lectura

El DOM System.Text.Json no puede agregar, quitar o modificar elementos JSON. Está diseñado de esta manera para favorecer el rendimiento y para reducir las asignaciones para el análisis de los tamaños comunes de carga de JSON (es decir, < 1 MB). Si el escenario usa actualmente un DOM modificable, una de las siguientes soluciones alternativas podría ser factible:

  • Para compilar un JsonDocument desde cero (es decir, sin pasar una carga de JSON existente al método Parse), escriba el texto JSON mediante Utf8JsonWriter y analice la salida del mismo para crear un nuevo JsonDocument.
  • Para modificar un JsonDocument existente, úselo para escribir texto JSON, realizar cambios mientras escribe y analizar la salida del mismo para crear un nuevo JsonDocument.
  • Para combinar documentos JSON existentes, equivalentes a las API de JObject.Merge o JContainer.Merge desde Newtonsoft.Json, vea este problema de GitHub.

JsonElement es una estructura de unión

JsonDocument expone el RootElement como una propiedad de tipo JsonElement (que es una unión), un tipo de estructura que abarca cualquier elemento JSON. Newtonsoft.Json utiliza tipos jerárquicos dedicados como JObject, JArray, JToken, etc. JsonElement es lo que puede buscar y enumerar, y puede usar JsonElement para materializar los elementos JSON en tipos de .NET.

Cómo buscar subelementos en JsonDocument y JsonElement

Las búsquedas de tokens JSON mediante JObject o JArray desde Newtonsoft.Json suelen ser relativamente rápidas porque son búsquedas en algunos diccionarios. Por comparación, 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 mediante RootElement. En su lugar, busque objetos JSON anidados en función de la estructura conocida de los datos JSON. Por ejemplo, si busca una propiedad Grade en objetos Student, recorra los objetos Student y obtenga el valor de Grade para cada uno, en lugar de buscar en todos los objetos JsonElement en busca de propiedades Grade. Si lo hace, se pasará innecesariamente sobre los mismos datos.

Para obtener un ejemplo de código, vea Uso de JsonDocument para acceder a los datos.

Utf8JsonReader en comparación con JsonTextReader

System.Text.Json.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.

En las siguientes secciones se explican los patrones de programación recomendados para el uso de Utf8JsonReader.

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 porque necesita almacenar en caché la entrada ReadOnlySpan<byte> que, a su vez, 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. Esto difiere de Newtonsoft.Json debido a que el JsonTextReader de Newtonsoft.Json es una clase. Para más información sobre el uso de las estructuras de referencia, vea Escritura de código C# seguro y eficaz.

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 Stream o PipeReader

Utf8JsonReader admite la lectura desde ReadOnlySpan<byte> o ReadOnlySequence<byte> con codificación UTF-8 (que es el resultado de la lectura desde PipeReader).

Para la lectura sincrónica, puede leer la carga de JSON hasta el final de la secuencia en una matriz de bytes y pasarla al lector. Para leer de una cadena (que tiene codificación UTF-16), llame a UTF8.GetBytes para transcodificar primero la cadena en una matriz de bytes con codificación UTF-8. Después, páselo a Utf8JsonReader.

Dado que Utf8JsonReader considera que la entrada es texto JSON, se considera que una marca de orden de bytes (BOM) de UTF-8 no es válida. El autor de la llamada debe filtrarla antes de pasar los datos al lector.

Para obtener códigos de ejemplo, vea Uso de 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 avance 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

Newtonsoft.Json proporciona las API que devuelven Nullable<T>, como ReadAsBoolean, que controla un TokenType Null por usted devolviendo un valor bool?. 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();
}

Compatibilidad con múltiples versiones

Si necesita seguir usando Newtonsoft.Json para determinadas plataformas de destino, puede tener varias versiones y dos implementaciones. Pero esto no es algo trivial y requeriría algunos #ifdefs y la duplicación de orígenes. Una manera de compartir todo el código posible es crear un contenedor de ref struct alrededor de Utf8JsonReader y Newtonsoft.Json JsonTextReader. Dicho contenedor unificaría el área expuesta pública mientras aísla las diferencias de comportamiento. Esto le permite aislar los cambios principalmente en la construcción del tipo, junto con pasar el nuevo tipo por referencia. Este es el patrón que sigue la biblioteca de Microsoft.Extensions.DependencyModel:

Utf8JsonWriter en comparación con JsonTextWriter

System.Text.Json.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.

En las siguientes secciones se explican los patrones de programación recomendados para el uso de Utf8JsonWriter.

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 valores sin formato

El método WriteRawValue de Newtonsoft.Json escribe el JSON sin formato donde se espera un valor. System.Text.Json no tiene equivalente directo, pero esta es una solución alternativa que garantiza que solo se escribe un JSON válido:

using JsonDocument doc = JsonDocument.Parse(string);
doc.WriteTo(writer);

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.

Personalización del formato JSON

JsonTextWriter incluye la configuración siguiente, para la cual Utf8JsonWriter no tiene ningún equivalente:

  • Indentation: especifica el número de caracteres a los que se va a aplicar sangría. Utf8JsonWriter siempre realiza una sangría de dos caracteres.
  • IndentChar: especifica el carácter que se va a utilizar para la sangría. Utf8JsonWriter siempre usa el espacio en blanco.
  • QuoteChar: especifica el carácter que se va a usar para rodear los valores de cadena. Utf8JsonWriter siempre usa comillas dobles.
  • QuoteName: especifica si los nombres de propiedad deben encerrarse entre comillas. Utf8JsonWriter siempre los coloca entrecomillados.

No hay ninguna solución alternativa que permita personalizar el JSON generado por Utf8JsonWriter de estas maneras.

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

JsonTextWriter proporciona métodos WriteValue para los valores TimeSpan, URIy char. Utf8JsonWriter no tiene métodos equivalentes. En su lugar, dé formato a estos valores como cadenas (por ejemplo, llamando a ToString()) y llame a WriteStringValue.

Compatibilidad con múltiples versiones

Si necesita seguir usando Newtonsoft.Json para determinadas plataformas de destino, puede tener varias versiones y dos implementaciones. Pero esto no es algo trivial y requeriría algunos #ifdefs y la duplicación de orígenes. Una manera de compartir todo el código posible es crear un contenedor alrededor de Utf8JsonWriter y Newtonsoft JsonTextWriter. Dicho contenedor unificaría el área expuesta pública mientras aísla las diferencias de comportamiento. Esto le permite aislar los cambios principalmente en la construcción del tipo. La biblioteca de Microsoft.Extensions.DependencyModel sigue:

Recursos adicionales