Lenguaje

Usa tipos y propiedades inmutables

Un tipo inmutable es uno que impide que cambie los valores de propiedad o campo de un objeto después de crear una instancia. El tipo puede ser un registro, no tener propiedades o campos públicos, tener propiedades de solo lectura o tener propiedades con establecedores privados o de solo inicialización. System.String es un ejemplo de un tipo inmutable. System.Text.Json proporciona diferentes formas de deserializar JSON en tipos inmutables.

Constructores con parámetros

De forma predeterminada, System.Text.Json usa el constructor sin parámetros público predeterminado. Sin embargo, puede indicarle que use un constructor con parámetros, lo que permite deserializar una clase o estructura inmutables.

  • Para una clase, si el único constructor es un parametrizado, ese constructor se usará.

  • Para una estructura o una clase con varios constructores, especifique la que se va a usar aplicando el atributo [JsonConstructor]. Cuando no se usa el atributo , siempre se usa un constructor público sin parámetros si está presente.

    En el ejemplo siguiente se usa el atributo [JsonConstructor]:

    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    namespace ImmutableTypes
    {
        public struct Forecast
        {
            public DateTime Date { get; }
            public int TemperatureC { get; }
            public string Summary { get; }
    
            [JsonConstructor]
            public Forecast(DateTime date, int temperatureC, string summary) =>
                (Date, TemperatureC, Summary) = (date, temperatureC, summary);
        }
    
        public class Program
        {
            public static void Run()
            {
                string json = """
                    {
                        "date":"2020-09-06T11:31:01.923395-07:00",
                        "temperatureC":-1,
                        "summary":"Cold"
                    }
                    """;
                Console.WriteLine($"Input JSON: {json}");
    
                var options = JsonSerializerOptions.Web;
    
                Forecast forecast = JsonSerializer.Deserialize<Forecast>(json, options);
    
                Console.WriteLine($"forecast.Date: {forecast.Date}");
                Console.WriteLine($"forecast.TemperatureC: {forecast.TemperatureC}");
                Console.WriteLine($"forecast.Summary: {forecast.Summary}");
    
                string roundTrippedJson =
                    JsonSerializer.Serialize<Forecast>(forecast, options);
    
                Console.WriteLine($"Output JSON: {roundTrippedJson}");
            }
        }
    }
    
    // Produces output like the following example:
    //
    //Input JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    //forecast.Date: 9 / 6 / 2020 11:31:01 AM
    //forecast.TemperatureC: -1
    //forecast.Summary: Cold
    //Output JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    
    Imports System.Text.Json
    Imports System.Text.Json.Serialization
    
    Namespace ImmutableTypes
    
        Public Structure Forecast
            Public ReadOnly Property [Date] As Date
            Public ReadOnly Property TemperatureC As Integer
            Public ReadOnly Property Summary As String
    
            <JsonConstructor>
            Public Sub New([Date] As Date, TemperatureC As Integer, Summary As String)
                Me.Date = [Date]
                Me.TemperatureC = TemperatureC
                Me.Summary = Summary
            End Sub
    
        End Structure
    
        Public NotInheritable Class Program
    
            Public Shared Sub Main()
                Dim json As String = "{""date"":""2020-09-06T11:31:01.923395-07:00"",""temperatureC"":-1,""summary"":""Cold""}"
                Console.WriteLine($"Input JSON: {json}")
    
                Dim forecast1 As Forecast = JsonSerializer.Deserialize(Of Forecast)(json, JsonSerializerOptions.Web)
    
                Console.WriteLine($"forecast.Date: {forecast1.[Date]}")
                Console.WriteLine($"forecast.TemperatureC: {forecast1.TemperatureC}")
                Console.WriteLine($"forecast.Summary: {forecast1.Summary}")
    
                Dim roundTrippedJson As String = JsonSerializer.Serialize(forecast1, JsonSerializerOptions.Web)
    
                Console.WriteLine($"Output JSON: {roundTrippedJson}")
            End Sub
    
        End Class
    
    End Namespace
    
    ' Produces output like the following example:
    '
    'Input JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    'forecast.Date: 9 / 6 / 2020 11:31:01 AM
    'forecast.TemperatureC: -1
    'forecast.Summary: Cold
    'Output JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    

    En .NET 7 y versiones anteriores, el atributo [JsonConstructor] solo se puede usar con constructores públicos.

En .NET 8 y versiones posteriores, el modo de reflexión admite constructores no públicos marcados con [JsonConstructor]. A partir de .NET 11, el modo de generación de código fuente también los admite.

Los nombres de parámetro de un constructor con parámetros deben coincidir con los nombres y tipos de propiedad. La coincidencia no es sensible a mayúsculas y minúsculas, y el parámetro del constructor debe coincidir con el nombre real de la propiedad, incluso si usa [JsonPropertyName] para renombrar una propiedad. En el ejemplo siguiente, el nombre de la propiedad TemperatureC se cambia a celsius en el CÓDIGO JSON, pero el parámetro del constructor todavía se denomina temperatureC:

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

namespace ImmutableTypesCtorParms
{
    public readonly struct Forecast
    {
        public DateTime Date { get; }
        [JsonPropertyName("celsius")]
        public int TemperatureC { get; }
        public string Summary { get; }

        [JsonConstructor]
        public Forecast(DateTime date, int temperatureC, string summary) =>
            (Date, TemperatureC, Summary) = (date, temperatureC, summary);
    }

    public class Program
    {
        public static void Run()
        {
            string json = """
                {
                    "date":"2020-09-06T11:31:01.923395-07:00",
                    "celsius":-1,
                    "summary":"Cold"
                }
                """;
            Console.WriteLine($"Input JSON: {json}");

            var options = JsonSerializerOptions.Web;

            Forecast forecast = JsonSerializer.Deserialize<Forecast>(json, options);

            Console.WriteLine($"forecast.Date: {forecast.Date}");
            Console.WriteLine($"forecast.TemperatureC: {forecast.TemperatureC}");
            Console.WriteLine($"forecast.Summary: {forecast.Summary}");

            string roundTrippedJson =
                JsonSerializer.Serialize<Forecast>(forecast, options);

            Console.WriteLine($"Output JSON: {roundTrippedJson}");

        }
    }
}

// Produces output like the following example:
//
//Input JSON: { "date":"2020-09-06T11:31:01.923395-07:00","celsius":-1,"summary":"Cold"}
//forecast.Date: 9 / 6 / 2020 11:31:01 AM
//forecast.TemperatureC: -1
//forecast.Summary: Cold
//Output JSON: { "date":"2020-09-06T11:31:01.923395-07:00","celsius":-1,"summary":"Cold"}

Además de [JsonPropertyName], los atributos siguientes admiten la deserialización con constructores con parámetros:

Parámetros por referencia del constructor

A partir de .NET 11, JsonSerializer deserializa tipos cuyos parámetros de constructor usan los modificadores in, ref, out y ref readonly.

Modificador de parámetros Comportamiento de la deserialización
in, ref y ref readonly El serializador enlaza cada parámetro por nombre y usa su tipo de elemento subyacente para la coincidencia de tipos.
out El serializador no enlaza el parámetro a JSON. Descarta el valor que asigna el constructor.

En el constructor siguiente, el serializador enlaza temperatureC desde JSON. No vincula isValid:

public Forecast(in int temperatureC, out bool isValid)
{
    TemperatureC = temperatureC;
    isValid = true;
}

En Visual Basic, un ByRef parámetro de constructor sigue el ref comportamiento que se muestra en la tabla:

Public Sub New(ByRef temperatureC As Integer)
    TemperatureC = temperatureC
End Sub

Archivo

También se admiten registros para la serialización y deserialización, como se muestra en el ejemplo siguiente:

using System.Text.Json;

namespace Records
{
    public record Forecast(DateTime Date, int TemperatureC)
    {
        public string? Summary { get; init; }
    };

    public class Program
    {
        public static void Run()
        {
            Forecast forecast = new(DateTime.Now, 40)
            {
                Summary = "Hot!"
            };

            string forecastJson = JsonSerializer.Serialize<Forecast>(forecast);
            Console.WriteLine(forecastJson);
            Forecast? forecastObj = JsonSerializer.Deserialize<Forecast>(forecastJson);
            Console.WriteLine(forecastObj);
        }
    }
}

// Produces output like the following example:
//
//{ "Date":"2020-10-21T15:26:10.5044594-07:00","TemperatureC":40,"Summary":"Hot!"}
//Forecast { Date = 10 / 21 / 2020 3:26:10 PM, TemperatureC = 40, Summary = Hot! }

Puede aplicar cualquiera de los atributos a los nombres de propiedad mediante el property: destino en el atributo. Para obtener más información sobre registros posicionales, consulte el artículo sobre los registros en la referencia de lenguaje C#.

Descriptores de acceso de propiedad y miembros no públicos

Puede habilitar el uso de un descriptor de acceso no público en una propiedad mediante el atributo [JsonInclude], como se muestra en el ejemplo siguiente:

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

namespace NonPublicAccessors
{
    public class Forecast
    {
        public DateTime Date { get; init; }

        [JsonInclude]
        public int TemperatureC { get; private set; }

        [JsonInclude]
        public string? Summary { private get; set; }
    };

    public class Program
    {
        public static void Run()
        {
            string json = """
                {
                    "Date":"2020-10-23T09:51:03.8702889-07:00",
                    "TemperatureC":40,
                    "Summary":"Hot"
                }
                """;
            Console.WriteLine($"Input JSON: {json}");

            Forecast forecastDeserialized = JsonSerializer.Deserialize<Forecast>(json)!;
            Console.WriteLine($"Date: {forecastDeserialized.Date}");
            Console.WriteLine($"TemperatureC: {forecastDeserialized.TemperatureC}");

            json = JsonSerializer.Serialize<Forecast>(forecastDeserialized);
            Console.WriteLine($"Output JSON: {json}");
        }
    }
}

// Produces output like the following example:
//
//Input JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}
//Date: 10 / 23 / 2020 9:51:03 AM
//TemperatureC: 40
//Output JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}
Imports System.Text.Json
Imports System.Text.Json.Serialization

Namespace NonPublicAccessors

    Public Class Forecast
        Public Property [Date] As Date

        Private _temperatureC As Integer

        <JsonInclude>
        Public Property TemperatureC As Integer
            Get
                Return _temperatureC
            End Get
            Private Set(Value As Integer)
                _temperatureC = Value
            End Set
        End Property

        Private _summary As String

        <JsonInclude>
        Public Property Summary As String
            Private Get
                Return _summary
            End Get
            Set(Value As String)
                _summary = Value
            End Set
        End Property

    End Class

    Public NotInheritable Class Program

        Public Shared Sub Main()
            Dim json As String = "{""Date"":""2020-10-23T09:51:03.8702889-07:00"",""TemperatureC"":40,""Summary"":""Hot""}"
            Console.WriteLine($"Input JSON: {json}")

            Dim forecastDeserialized As Forecast = JsonSerializer.Deserialize(Of Forecast)(json)
            Console.WriteLine($"Date: {forecastDeserialized.[Date]}")
            Console.WriteLine($"TemperatureC: {forecastDeserialized.TemperatureC}")

            json = JsonSerializer.Serialize(forecastDeserialized)
            Console.WriteLine($"Output JSON: {json}")
        End Sub

    End Class

End Namespace

' Produces output like the following example:
'
'Input JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}
'Date: 10 / 23 / 2020 9:51:03 AM
'TemperatureC: 40
'Output JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}

Al incluir una propiedad con un establecedor privado, todavía puede deserializar esa propiedad.

En .NET 8 y versiones posteriores, también puede usar el atributo [JsonInclude] para incluir miembros no públicos en el contrato de serialización para un tipo determinado.

A partir de .NET 11, la generación de código fuente admite miembros private, internal y protected marcados con [JsonInclude]. También admite private, internal y protected accesores para propiedades que se marquen con [JsonInclude]. La generación de origen también admite constructores inaccesibles marcados con [JsonConstructor].

Nota

En .NET 10 y versiones anteriores, la generación de código fuente no admite miembros ni descriptores de acceso private o protected. La aplicación del atributo [JsonInclude] al miembro o la propiedad no quita esta limitación. La generación de código fuente admite miembros y descriptores de acceso internal solo cuando se encuentran en el mismo ensamblado que el JsonSerializerContext generado. No admite constructores inaccesibles, incluso cuando se marcan con [JsonConstructor].

Propiedades solo de inicialización

System.Text.Json deserializa las propiedades de solo init como cualquier otra propiedad configurable. A partir de .NET 11, un setter generado a partir del código fuente solo se ejecuta cuando la carga útil JSON contiene la propiedad. Una propiedad omitida conserva su valor de inicializador.

Propiedades de solo lectura

En .NET 8 y versiones posteriores, también se pueden deserializar las propiedades de solo lectura o las que no tienen establecedor privado o público. Aunque no se puede cambiar la instancia a la que hace referencia la propiedad, si el tipo de la propiedad es mutable, puede modificarla. Por ejemplo, puede agregar un elemento a una lista. Para deserializar una propiedad de solo lectura, debe establecer su comportamiento de control de creación de objetos en rellenar en lugar de reemplazar. Por ejemplo, puede anotar la propiedad con el atributo JsonObjectCreationHandlingAttribute.

class A
{
    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
    public List<int> Numbers1 { get; } = new List<int>() { 1, 2, 3 };
}

Para obtener más información, vea Rellenar las propiedades inicializadas.

Consulte también