Idioma

Usar tipos e propriedades imutáveis

Um tipo imutável é aquele que impede você de alterar quaisquer valores de propriedade ou campo de um objeto após a sua instanciação. O tipo pode ser um registro, não ter propriedades ou campos públicos, ter propriedades somente leitura ou ter propriedades com setters privados ou somente init. System.String é um exemplo de um tipo imutável. System.Text.Json fornece diferentes maneiras de desserializar o JSON para tipos imutáveis.

Construtores parametrizados

Por padrão, System.Text.Json usa o construtor público sem parâmetros padrão. No entanto, você pode instruí-lo a usar um construtor parametrizado, o que torna possível deserializar uma classe ou struct imutável.

  • Para uma classe, se o único construtor for parametrizado, esse construtor será usado.

  • Para um struct ou uma classe com vários construtores, especifique aquele a ser usado aplicando o atributo [JsonConstructor]. Quando o atributo não é usado, um construtor público sem parâmetros é sempre usado se presente.

    O exemplo a seguir usa o 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"}
    

    No .NET 7 e versões anteriores, o atributo [JsonConstructor] só pode ser usado com construtores públicos.

No .NET 8 e versões posteriores, o modo de reflexão dá suporte a construtores não públicos marcados com [JsonConstructor]. A partir do .NET 11, o modo de geração de código-fonte também oferece suporte a elas.

Os nomes de parâmetro de um construtor parametrizado devem corresponder aos nomes e tipos de propriedade. A correspondência não diferencia maiúsculas de minúsculas e o parâmetro do construtor deverá corresponder ao nome da propriedade real, mesmo que você use [JsonPropertyName] para renomear uma propriedade. No exemplo a seguir, o nome da propriedade TemperatureC é alterado para celsius no JSON, mas o parâmetro do construtor ainda é nomeado 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"}

Além de [JsonPropertyName], os seguintes atributos dão suporte à desserialização com construtores parametrizados:

Parâmetros de construtor por referência

A partir do .NET 11, JsonSerializer desserializa tipos cujos parâmetros do construtor usam os modificadores ref readonly, ref, in e out.

Modificador de parâmetro Comportamento de desserialização
in, ref e ref readonly O serializador associa cada parâmetro por nome e usa seu tipo de elemento subjacente para correspondência de tipo.
out O serializador não associa o parâmetro ao JSON. Ele descarta o valor que o construtor atribui.

No construtor a seguir, o serializador faz o vínculo de temperatureC a partir do JSON. Ele não vincula isValid:

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

No Visual Basic, um ByRef parâmetro de construtor segue o ref comportamento mostrado na tabela:

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

Arquivos

Também há suporte para registros para serialização e desserialização, conforme mostrado no exemplo a seguir:

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! }

É possível aplicar qualquer um dos atributos aos nomes de propriedade, usando o destino property: no atributo. Para obter mais informações sobre registros posicionais, consulte o artigo sobre registros na referência de linguagem C#.

Membros e acessadores de propriedade não públicos

Você pode habilitar o uso de um acessador de não público em uma propriedade usando o atributo [JsonInclude], conforme mostrado no exemplo a seguir:

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"}

Ao incluir uma propriedade com um setter privado, você ainda pode desserializar essa propriedade.

No .NET 8 e versões posteriores, você também pode usar o atributo [JsonInclude] para optar por membros não públicos no contrato de serialização para um determinado tipo.

A partir do .NET 11, a geração de código-fonte é compatível com membros internal, protected e private que você marca com [JsonInclude]. Ele também oferece suporte a acessores private, internal e protected em propriedades que você marca com [JsonInclude]. A geração de origem também dá suporte a construtores inacessíveis marcados com [JsonConstructor].

Nota

No .NET 10 e em versões anteriores, a geração de código-fonte não dá suporte a membros ou acessadores private ou protected. A aplicação do atributo [JsonInclude] ao membro ou à propriedade não remove essa limitação. A geração de código-fonte suporta membros e acessadores internal somente quando eles estão no mesmo assembly que o JsonSerializerContext gerado. Ele não dá suporte a construtores inacessíveis, mesmo quando você os marca com [JsonConstructor].

Propriedades somente de inicialização

System.Text.Json desserializa propriedades somente init como qualquer outra propriedade configurável. Começando no .NET 11, um setter gerado pela origem é executado somente quando o conteúdo JSON contém a propriedade. Uma propriedade omitida mantém seu valor de inicialização.

Propriedades somente leitura

No .NET 8 e versões posteriores, as propriedades somente leitura ou aquelas que não têm nenhum setter, privadas ou públicas, também podem ser desserializadas. Embora você não possa alterar a instância que a propriedade faz referência, se o tipo da propriedade for mutável, você poderá modificá-la. Por exemplo, você pode adicionar um elemento a uma lista. Para desserializar uma propriedade somente leitura, você precisa definir seu comportamento de tratamento de criação de objeto para preencher em vez de substituir. Por exemplo, você pode anotar a propriedade com o atributo JsonObjectCreationHandlingAttribute.

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

Para obter mais informações, consulte Popular propriedades inicializadas.

Consulte também