Langue

Comment gérer le dépassement JSON ou utiliser JsonElement ou JsonNode

Cet article montre comment gérer le débordement JSON avec l’espace de noms System.Text.Json. Il montre également comment désérialiser dans JsonElement ou JsonNode, en tant qu’alternative pour d’autres scénarios où le type cible peut ne pas correspondre parfaitement à tout le JSON en cours de désérialisation.

Gérer le débordement JSON

Lors de la désérialisation, vous pouvez recevoir des données dans le JSON qui ne sont pas représentées par les propriétés du type cible. Par exemple, supposons que votre type cible est le suivant :

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
Public Class WeatherForecast
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class

Et que le JSON à désérialiser est le suivant :

{
  "Date": "2019-08-01T00:00:00-07:00",
  "temperatureCelsius": 25,
  "Summary": "Hot",
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}

Si vous désérialisez le JSON affiché dans le type affiché, les propriétés DatesAvailable et SummaryWords n’ont nulle part où aller et sont perdues. Pour capturer des données supplémentaires telles que ces propriétés, appliquez l’attribut [JsonExtensionData] à une propriété ou à un champ. Utilisez l’une de ces déclarations prises en charge :

Pour les données d’extension de dictionnaire mutables, utilisez n’importe quel type assignable à l’une des interfaces prises en charge IDictionary , telles que Dictionary<string, JsonElement>. Les déclarations en lecture seule doivent utiliser l’un des types d’interface exacts IReadOnlyDictionary répertoriés ci-dessus.

public class WeatherForecastWithExtensionData
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    [JsonExtensionData]
    public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}
Public Class WeatherForecastWithExtensionData
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String

    <JsonExtensionData>
    Public Property ExtensionData As Dictionary(Of String, Object)

End Class

Pour un membre de données d’extension IReadOnlyDictionary, .NET 11 instancie un Dictionary<string, object> ou un Dictionary<string, JsonElement>. Le membre doit être accessible en écriture, car le sérialiseur initialise un nouveau dictionnaire à partir des valeurs existantes, puis le réaffecte. Si une propriété JSON entrante duplique une clé existante, la valeur entrante gagne.

Le tableau suivant montre le résultat de la désérialisation du JSON indiqué précédemment dans cet exemple de type. Les données supplémentaires deviennent des paires clé-valeur de la propriété ExtensionData :

Propriété Valeur Notes
Date "8/1/2019 12:00:00 AM -07:00"
TemperatureCelsius 0 Non-correspondance de casse (temperatureCelsius dans le JSON), la propriété n’est donc pas définie.
Summary "Hot"
ExtensionData "temperatureCelsius": 25,
"DatesAvailable": ["2019-08-01T00:00:00-07:00","2019-08-02T00:00:00-07:00"],
"SummaryWords": ["Cool","Windy","Humid"]
Étant donné que la casse ne correspond pas, temperatureCelsius est considéré comme un élément supplémentaire et devient une paire clé-valeur dans le dictionnaire.
Chaque tableau supplémentaire du JSON devient une paire clé-valeur, avec un tableau comme objet valeur.

Lorsque l’objet cible est sérialisé, les paires de valeurs de clé de données d’extension deviennent des propriétés JSON comme elles l’étaient dans le JSON entrant :

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 0,
  "Summary": "Hot",
  "temperatureCelsius": 25,
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}

Notez que le nom de la propriété ExtensionData n’apparaît pas dans le JSON. Ce comportement permet au JSON d’effectuer un aller-retour sans perdre de données supplémentaires qui, sinon, ne seraient pas désérialisées.

À partir de .NET 11, la sérialisation aplatit les données d’extension JsonObject dans l’objet JSON parent. Les JsonObject propriétés apparaissent directement dans l’objet conteneur, et non sous le nom du membre de données d’extension.

L’exemple suivant montre un aller-retour de JSON vers un objet désérialisé, puis à nouveau vers JSON :

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

namespace RoundtripExtensionData
{
    public class WeatherForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        [JsonExtensionData]
        public Dictionary<string, JsonElement>? ExtensionData { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00-07:00"",
  ""temperatureCelsius"": 25,
  ""Summary"": ""Hot"",
  ""SummaryField"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00-07:00"",
    ""2019-08-02T00:00:00-07:00""
  ],
  ""SummaryWords"": [
    ""Cool"",
    ""Windy"",
    ""Humid""
  ]
}";
            WeatherForecast weatherForecast = 
                JsonSerializer.Deserialize<WeatherForecast>(jsonString)!;

            var serializeOptions = new JsonSerializerOptions { WriteIndented = true };
            jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
            Console.WriteLine($"JSON output:\n{jsonString}\n");
        }
    }
}
// output:
//JSON output:
//{
//  "Date": "2019-08-01T00:00:00-07:00",
//  "TemperatureCelsius": 0,
//  "Summary": "Hot",
//  "temperatureCelsius": 25,
//  "SummaryField": "Hot",
//  "DatesAvailable": [
//    "2019-08-01T00:00:00-07:00",
//    "2019-08-02T00:00:00-07:00"
//  ],
//  "SummaryWords": [
//    "Cool",
//    "Windy",
//    "Humid"
//  ]
//}

Désérialiser dans JsonElement ou JsonNode

Si vous souhaitez simplement être flexible quant à ce que le JSON accepte pour une propriété particulière, une alternative consiste à désérialiser dans JsonElement ou JsonNode. Toute propriété JSON valide peut être désérialisée dans JsonElement ou JsonNode. Choisissez JsonElement pour créer un objet immuable ou JsonNode pour créer un objet mutable.

L’exemple suivant montre un aller-retour de JSON et de retour vers JSON pour une classe qui inclut des propriétés de type JsonElement et JsonNode.

using System.Text.Json;
using System.Text.Json.Nodes;

namespace RoundtripJsonElementAndNode
{
    public class WeatherForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        public JsonElement DatesAvailable { get; set; }
        public JsonNode? SummaryWords { get; set; }
    }
        public class Program
    {
        public static void Main()
        {
            string jsonString =
@"{
  ""Date"": ""2019-08-01T00:00:00-07:00"",
  ""TemperatureCelsius"": 25,
  ""Summary"": ""Hot"",
  ""DatesAvailable"": [
    ""2019-08-01T00:00:00-07:00"",
    ""2019-08-02T00:00:00-07:00""
  ],
  ""SummaryWords"": [
    ""Cool"",
    ""Windy"",
    ""Humid""
  ]
}";
            WeatherForecast? weatherForecast = 
                JsonSerializer.Deserialize<WeatherForecast>(jsonString);

            var serializeOptions = new JsonSerializerOptions { WriteIndented = true };
            jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
            Console.WriteLine(jsonString);
        }
    }
}
// output:
//{
//  "Date": "2019-08-01T00:00:00-07:00",
//  "TemperatureCelsius": 25,
//  "Summary": "Hot",
//  "DatesAvailable": [
//    "2019-08-01T00:00:00-07:00",
//    "2019-08-02T00:00:00-07:00"
//  ],
//  "SummaryWords": [
//    "Cool",
//    "Windy",
//    "Humid"
//  ]
//}

Voir aussi