Langue

Utiliser des types et des propriétés immuables

Un type immuable est un type qui vous empêche de modifier les valeurs de propriété ou de champ d’un objet après son instanciation. Le type peut être un enregistrement, ne pas avoir de propriétés ou de champs publics, avoir des propriétés en lecture seule ou avoir des propriétés avec des paramètres privés ou init-only. System.String est un exemple de type immuable. System.Text.Json fournit différentes façons de désérialiser JSON en types immuables.

Constructeurs paramétrés

Par défaut, System.Text.Json utilise le constructeur public sans paramètre par défaut. Toutefois, vous pouvez lui indiquer d’utiliser un constructeur paramétrable, ce qui permet de désérialiser une classe ou un struct immuable.

  • Pour une classe, si le seul constructeur est paramétrable, ce constructeur sera utilisé.

  • Pour un struct ou une classe avec plusieurs constructeurs, spécifiez celle à utiliser en appliquant l’attribut [JsonConstructor]. Lorsque l’attribut n’est pas utilisé, un constructeur sans paramètre public est toujours utilisé s’il est présent.

    L’exemple suivant utilise l’attribut [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"}
    

    Dans .NET 7 et versions antérieures, l’attribut [JsonConstructor] ne peut être utilisé qu’avec des constructeurs publics.

Dans .NET 8 et versions ultérieures, le mode réflexion prend en charge les constructeurs non publics marqués par [JsonConstructor]. À compter de .NET 11, le mode de génération source les prend également en charge.

Les noms de paramètres d’un constructeur paramétrable doivent correspondre aux noms et types de propriétés. La correspondance ne respecte pas la casse et le paramètre du constructeur doit correspondre au nom réel de la propriété même si vous utilisez [JsonPropertyName] pour renommer une propriété. Dans l’exemple suivant, le nom de la propriété TemperatureC est remplacé par celsius dans le json, mais le paramètre du constructeur est toujours nommé 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"}

Outre [JsonPropertyName], les attributs suivants prennent en charge la désérialisation avec des constructeurs paramétrables :

Paramètres du constructeur par référence

À compter de .NET 11, JsonSerializer désérialise les types dont les paramètres du constructeur utilisent les modificateurs out, ref readonly, in et ref.

Modificateur de paramètre Comportement de désérialisation
in, ref et ref readonly Le sérialiseur lie chaque paramètre par nom et utilise son type d’élément sous-jacent pour la correspondance de type.
out Le sérialiseur ne lie pas le paramètre au format JSON. Elle rejette la valeur assignée par le constructeur.

Dans le constructeur suivant, le sérialiseur lie temperatureC à partir du JSON. Elle ne lie isValidpas :

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

Dans Visual Basic, un ByRef paramètre de constructeur suit le ref comportement indiqué dans le tableau :

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

Enregistrements

Les enregistrements sont également pris en charge pour la sérialisation et la désérialisation, comme illustré dans l’exemple suivant :

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

Vous pouvez appliquer l’un des attributs aux noms de propriétés à l’aide de la cible property: sur l’attribut. Pour plus d'informations sur les enregistrements positionnels, consultez l'article sur les enregistrements dans la référence de langage C#.

Membres non publics et accesseurs de propriétés

Vous pouvez activer l’utilisation d’un accesseur de non public sur une propriété à l’aide de l’attribut [JsonInclude], comme illustré dans l’exemple suivant :

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

En incluant une propriété avec un setter privé, vous pouvez toujours désérialiser cette propriété.

Dans .NET 8 et les versions ultérieures, vous pouvez également utiliser l'attribut [JsonInclude] pour inclure des membres non publics dans le contrat de sérialisation d'un type donné.

À compter de .NET 11, la génération de code source prend en charge les membres private, internal et protected que vous annotez avec [JsonInclude]. Il prend également en charge les accesseurs private, internal et protected pour les propriétés que vous annotez avec [JsonInclude]. La génération de source prend également en charge les constructeurs inaccessibles marqués avec [JsonConstructor].

Remarque

Dans .NET 10 et les versions antérieures, la génération de source ne prend pas en charge les membres ou accesseurs private ou protected. L’application de l’attribut [JsonInclude] au membre ou à la propriété ne supprime pas cette limitation. La génération source prend en charge les membres et accesseurs internal uniquement lorsqu’ils se trouvent dans le même assembly que le JsonSerializerContext généré. Il ne prend pas en charge les constructeurs inaccessibles, même lorsque vous les marquez avec [JsonConstructor].

Propriétés init-only

System.Text.Json désérialise les propriétés init uniquement comme n’importe quelle autre propriété définissable. À compter de .NET 11, un setter généré par la source s’exécute uniquement lorsque la charge utile JSON contient la propriété. Une propriété omise conserve sa valeur d’initialiseur.

Propriétés en lecture seule

Dans .NET 8 et versions ultérieures, les propriétés en lecture seule, ou celles qui n’ont pas de setter privé ou public, peuvent également être désérialisées. Bien que vous ne puissiez pas modifier l’instance référencée par la propriété, si le type de la propriété est mutable, vous pouvez le modifier. Par exemple, vous pouvez ajouter un élément à une liste. Pour désérialiser une propriété en lecture seule, vous devez définir son comportement de gestion de la création d’objets pour remplir au lieu de remplacer. Par exemple, vous pouvez annoter la propriété avec l’attribut JsonObjectCreationHandlingAttribute.

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

Pour plus d’informations, consultez Remplir les propriétés initialisées.

Voir aussi