Comment sérialiser les propriétés des classes dérivées avec System.Text.Json
Dans cet article, vous apprendrez à sérialiser les propriétés des classes dérivées avec le namespace System.Text.Json
.
Sérialiser les propriétés des classes dérivées
Dans les versions antérieures à .NET 7, System.Text.Json
ne prend pas en charge la sérialisation des hiérarchies de types polymorphes. Par exemple, si le type d’une propriété est une interface ou une classe abstraite, seules les propriétés définies sur l’interface ou la classe abstraite sont sérialisées, même si le type de runtime a des propriétés supplémentaires. Les exceptions à ce comportement sont expliquées dans cette section. Pour plus d’informations sur la prise en charge dans .NET 7, consultez Sérialisation polymorphe dans .NET 7.
Par exemple, supposons que vous avez une classe WeatherForecast
et une classe dérivée WeatherForecastDerived
:
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
public class WeatherForecastDerived : WeatherForecast
{
public int WindSpeed { get; set; }
}
Public Class WeatherForecastDerived
Inherits WeatherForecast
Public Property WindSpeed As Integer
End Class
Et supposons que l’argument de type de la méthode Serialize
au moment de la compilation est WeatherForecast
:
var options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecast>(weatherForecast, options);
Dim options As JsonSerializerOptions = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)
Dans ce scénario, la propriété WindSpeed
n’est pas sérialisée même si l’objet weatherForecast
est un objet WeatherForecastDerived
. Seules les propriétés de classe de base sont sérialisées :
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Ce comportement est destiné à empêcher l’exposition accidentelle de données dans un type dérivé créé au runtime.
Pour sérialiser les propriétés du type dérivé dans l’exemple précédent, utilisez l’une des approches suivantes :
Appelez une surcharge de Serialize qui vous permet de spécifier le type au moment du runtime :
options = new JsonSerializerOptions { WriteIndented = true }; jsonString = JsonSerializer.Serialize(weatherForecast, weatherForecast.GetType(), options);
options = New JsonSerializerOptions With { .WriteIndented = True } jsonString = JsonSerializer.Serialize(weatherForecast1, weatherForecast1.[GetType](), options)
Déclarez l’objet à sérialiser comme
object
.options = new JsonSerializerOptions { WriteIndented = true }; jsonString = JsonSerializer.Serialize<object>(weatherForecast, options);
options = New JsonSerializerOptions With { .WriteIndented = True } jsonString = JsonSerializer.Serialize(Of Object)(weatherForecast1, options)
Dans l’exemple de scénario précédent, les deux approches entraînent l’inclusion de la propriété WindSpeed
dans la sortie JSON :
{
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Important
Ces approches fournissent une sérialisation polymorphe uniquement pour l’objet racine à sérialiser, pas pour les propriétés de cet objet racine.
Vous pouvez obtenir une sérialisation polymorphe pour les objets de niveau inférieur si vous les définissez comme type object
. Par exemple, supposons que votre classe WeatherForecast
a une propriété nommée PreviousForecast
qui peut être définie comme type WeatherForecast
ou object
:
public class WeatherForecastWithPrevious
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
public WeatherForecast? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPrevious
Public Property [Date] As DateTimeOffset
Public Property TemperatureCelsius As Integer
Public Property Summary As String
Public Property PreviousForecast As WeatherForecast
End Class
public class WeatherForecastWithPreviousAsObject
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
public object? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPreviousAsObject
Public Property [Date] As DateTimeOffset
Public Property TemperatureCelsius As Integer
Public Property Summary As String
Public Property PreviousForecast As Object
End Class
Si la propriété PreviousForecast
contient une instance de WeatherForecastDerived
:
- La sortie JSON de la sérialisation
WeatherForecastWithPrevious
n’inclut pasWindSpeed
. - La sortie JSON de la sérialisation
WeatherForecastWithPreviousAsObject
inclutWindSpeed
.
Pour sérialiser WeatherForecastWithPreviousAsObject
, il n’est pas nécessaire d’appeler Serialize<object>
ou GetType
parce que l’objet racine n’est pas celui qui peut être d’un type dérivé. L’exemple de code suivant n’appelle pas Serialize<object>
ou GetType
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject1, options)
Le code précédent sérialise WeatherForecastWithPreviousAsObject
correctement :
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"PreviousForecast": {
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
}
La même approche de la définition des propriétés que object
fonctionne avec les interfaces. Supposons que vous avez l’interface et l’implémentation suivantes, et que vous souhaitez sérialiser une classe avec des propriétés qui contiennent des instances d’implémentation :
namespace SystemTextJsonSamples
{
public interface IForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
public class Forecast : IForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
public int WindSpeed { get; set; }
}
public class Forecasts
{
public IForecast? Monday { get; set; }
public object? Tuesday { get; set; }
}
}
Namespace SystemTextJsonSamples
Public Interface IForecast
Property [Date] As DateTimeOffset
Property TemperatureCelsius As Integer
Property Summary As String
End Interface
Public Class Forecast
Implements IForecast
Public Property [Date] As DateTimeOffset Implements IForecast.[Date]
Public Property TemperatureCelsius As Integer Implements IForecast.TemperatureCelsius
Public Property Summary As String Implements IForecast.Summary
Public Property WindSpeed As Integer
End Class
Public Class Forecasts
Public Property Monday As IForecast
Public Property Tuesday As Object
End Class
End Namespace
Lorsque vous sérialisez une instance de Forecasts
, seul Tuesday
montre la propriété WindSpeed
parce que Tuesday
est défini comme object
:
var forecasts = new Forecasts
{
Monday = new Forecast
{
Date = DateTime.Parse("2020-01-06"),
TemperatureCelsius = 10,
Summary = "Cool",
WindSpeed = 8
},
Tuesday = new Forecast
{
Date = DateTime.Parse("2020-01-07"),
TemperatureCelsius = 11,
Summary = "Rainy",
WindSpeed = 10
}
};
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(forecasts, options);
Dim forecasts1 As New Forecasts With {
.Monday = New Forecast With {
.[Date] = Date.Parse("2020-01-06"),
.TemperatureCelsius = 10,
.Summary = "Cool",
.WindSpeed = 8
},
.Tuesday = New Forecast With {
.[Date] = Date.Parse("2020-01-07"),
.TemperatureCelsius = 11,
.Summary = "Rainy",
.WindSpeed = 10
}
}
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(forecasts1, options)
L’exemple suivant montre le JSON qui résulte du code précédent :
{
"Monday": {
"Date": "2020-01-06T00:00:00-08:00",
"TemperatureCelsius": 10,
"Summary": "Cool"
},
"Tuesday": {
"Date": "2020-01-07T00:00:00-08:00",
"TemperatureCelsius": 11,
"Summary": "Rainy",
"WindSpeed": 10
}
}
Notes
Cet article traite de la sérialisation, pas de la désérialisation. La désérialisation polymorphe n’est pas prise en charge dans les versions antérieures à .NET 7, mais comme solution de contournement, vous pouvez écrire un convertisseur personnalisé, comme l’exemple dans Prise en charge de la désérialisation polymorphe. Pour plus d’informations sur la façon dont .NET 7 prend en charge la sérialisation et la désérialisation polymorphes, consultez Guide pratique pour sérialiser les propriétés des classes dérivées avec System.Text.Json dans .NET 7.
À compter de .NET 7, System.Text.Json
prend en charge la sérialisation et la désérialisation des hiérarchies de types polymorphes avec des annotations d’attributs.
Attribut | Description |
---|---|
JsonDerivedTypeAttribute | Lorsqu’il est placé sur une déclaration de type, indique que le sous-type spécifié doit être choisi dans la sérialisation polymorphe. Il expose également la possibilité de spécifier un discriminateur de type. |
JsonPolymorphicAttribute | Lorsqu’il est placé sur une déclaration de type, indique que le type doit être sérialisé de façon polymorphe. Il expose également différentes options pour configurer la sérialisation et la désérialisation polymorphes pour ce type. |
Par exemple, supposons que vous avez une classe WeatherForecastBase
et une classe dérivée WeatherForecastWithCity
:
[JsonDerivedType(typeof(WeatherForecastWithCity))]
public class WeatherForecastBase
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastWithCity))>
Public Class WeatherForecastBase
Public Property [Date] As DateTimeOffset
Public Property TemperatureCelsius As Integer
Public Property Summary As String
End Class
public class WeatherForecastWithCity : WeatherForecastBase
{
public string? City { get; set; }
}
Public Class WeatherForecastWithCity
Inherits WeatherForecastBase
Public Property City As String
End Class
Et supposons que l’argument de type de la méthode Serialize<TValue>
au moment de la compilation est WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
Dans ce scénario, la propriété City
est sérialisée parce que l’objet weatherForecastBase
est en fait un objet WeatherForecastWithCity
. Cette configuration autorise la sérialisation polymorphe pour WeatherForecastBase
, en particulier lorsque le type de runtime est WeatherForecastWithCity
:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Même si l’aller-retour de la charge utile comme WeatherForecastBase
est pris en charge, il ne se matérialise pas en tant que type de runtime de WeatherForecastWithCity
. Au lieu de cela, il se matérialise en tant que type de runtime de WeatherForecastBase
:
WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>("""
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
""");
Console.WriteLine(value is WeatherForecastWithCity); // False
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(@"
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}")
Console.WriteLine(value is WeatherForecastWithCity) // False
La section suivante explique comment ajouter des métadonnées pour activer l’aller-retour du type dérivé.
Discriminateurs de type polymorphe
Pour activer la désérialisation polymorphe, vous devez spécifier un discriminateur de type pour la classe dérivée :
[JsonDerivedType(typeof(WeatherForecastBase), typeDiscriminator: "base")]
[JsonDerivedType(typeof(WeatherForecastWithCity), typeDiscriminator: "withCity")]
public class WeatherForecastBase
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
public class WeatherForecastWithCity : WeatherForecastBase
{
public string? City { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastBase), "base")>
<JsonDerivedType(GetType(WeatherForecastWithCity), "withCity")>
Public Class WeatherForecastBase
Public Property [Date] As DateTimeOffset
Public Property TemperatureCelsius As Integer
Public Property Summary As String
End Class
Public Class WeatherForecastWithCity
Inherits WeatherForecastBase
Public Property City As String
End Class
Avec les métadonnées ajoutées, plus précisément, le discriminateur de type, le sérialiseur peut sérialiser et désérialiser la charge utile en tant que type WeatherForecastWithCity
à partir de son type de base WeatherForecastBase
. La sérialisation émet du JSON avec les métadonnées du discriminant de type :
WeatherForecastBase weather = new WeatherForecastWithCity
{
City = "Milwaukee",
Date = new DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
TemperatureCelsius = 15,
Summary = "Cool"
}
var json = JsonSerializer.Serialize<WeatherForecastBase>(weather, options);
Console.WriteLine(json);
// Sample output:
// {
// "$type" : "withCity",
// "City": "Milwaukee",
// "Date": "2022-09-26T00:00:00-05:00",
// "TemperatureCelsius": 15,
// "Summary": "Cool"
// }
Dim weather As WeatherForecastBase = New WeatherForecastWithCity With
{
.City = "Milwaukee",
.[Date] = New DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
.TemperatureCelsius = 15,
.Summary = "Cool"
}
Dim json As String = JsonSerializer.Serialize(weather, options)
Console.WriteLine(json)
' Sample output:
' {
' "$type" : "withCity",
' "City": "Milwaukee",
' "Date": "2022-09-26T00:00:00-05:00",
' "TemperatureCelsius": 15,
' "Summary": "Cool"
' }
Avec le discriminateur de type, le sérialiseur peut désérialiser la charge utile de façon polymorphe comme WeatherForecastWithCity
:
WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>(json);
Console.WriteLine(value is WeatherForecastWithCity); // True
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(json)
Console.WriteLine(value is WeatherForecastWithCity) // True
Remarque
Par défaut, le discriminant $type
doit être placé au début de l’objet JSON, regroupé avec d’autres propriétés de métadonnées comme $id
et $ref
. Si vous lisez des données provenant d’une API externe qui place le discriminant $type
au milieu de l’objet JSON, définissez JsonSerializerOptions.AllowOutOfOrderMetadataProperties sur true
:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Soyez prudent lorsque vous activez cet indicateur, car cela pourrait entraîner un surdimensionnement des tampons (et des échecs de mémoire) lors de la désérialisation en flux de très gros objets JSON.
Combiner et mettre en correspondance les formats de discriminateur de type
Les identificateurs de discriminateur de type sont valides sous la forme string
ou int
de sorte que les éléments suivants sont valides :
[JsonDerivedType(typeof(WeatherForecastWithCity), 0)]
[JsonDerivedType(typeof(WeatherForecastWithTimeSeries), 1)]
[JsonDerivedType(typeof(WeatherForecastWithLocalNews), 2)]
public class WeatherForecastBase { }
var json = JsonSerializer.Serialize<WeatherForecastBase>(new WeatherForecastWithTimeSeries());
Console.WriteLine(json);
// Sample output:
// {
// "$type" : 1,
// Omitted for brevity...
// }
<JsonDerivedType(GetType(WeatherForecastWithCity), 0)>
<JsonDerivedType(GetType(WeatherForecastWithTimeSeries), 1)>
<JsonDerivedType(GetType(WeatherForecastWithLocalNews), 2)>
Public Class WeatherForecastBase
End Class
Dim json As String = JsonSerializer.Serialize(Of WeatherForecastBase)(New WeatherForecastWithTimeSeries())
Console.WriteLine(json)
' Sample output:
' {
' "$type" : 1,
' Omitted for brevity...
' }
Bien que l’API prenne en charge les combinaisons et correspondances des configurations de discriminateur de type, elle n’est pas recommandée. La recommandation générale est d’utiliser tous les discriminateurs de type string
, tous les discriminateurs de type int
, ou aucun discriminateur du tout. L’exemple suivant montre comment combiner et mettre en correspondance des configurations de discriminateur de type :
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: 3)]
[JsonDerivedType(typeof(FourDimensionalPoint), typeDiscriminator: "4d")]
public class BasePoint
{
public int X { get; set; }
public int Y { get; set; }
}
public class ThreeDimensionalPoint : BasePoint
{
public int Z { get; set; }
}
public sealed class FourDimensionalPoint : ThreeDimensionalPoint
{
public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint), 3)>
<JsonDerivedType(GetType(FourDimensionalPoint), "4d")>
Public Class BasePoint
Public Property X As Integer
Public Property Y As Integer
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
Public Property Z As Integer
End Class
Public NotInheritable Class FourDimensionalPoint
Inherits ThreeDimensionalPoint
Public Property W As Integer
End Class
Dans l’exemple précédent, le type BasePoint
n’a pas de discriminateur de type, tandis que le type ThreeDimensionalPoint
a un discriminateur de type int
et le type FourDimensionalPoint
a un discriminateur de type string
.
Important
Pour que la sérialisation polymorphe fonctionne, le type de la valeur sérialisée doit être celui du type de base polymorphe. Cela comprend l’utilisation du type de base comme paramètre de type générique lors de la sérialisation des valeurs de niveau racine, comme type déclaré de propriétés sérialisées ou comme élément de collection dans les collections sérialisées.
using System.Text.Json;
using System.Text.Json.Serialization;
PerformRoundTrip<BasePoint>();
PerformRoundTrip<ThreeDimensionalPoint>();
PerformRoundTrip<FourDimensionalPoint>();
static void PerformRoundTrip<T>() where T : BasePoint, new()
{
var json = JsonSerializer.Serialize<BasePoint>(new T());
Console.WriteLine(json);
BasePoint? result = JsonSerializer.Deserialize<BasePoint>(json);
Console.WriteLine($"result is {typeof(T)}; // {result is T}");
Console.WriteLine();
}
// Sample output:
// { "X": 541, "Y": 503 }
// result is BasePoint; // True
//
// { "$type": 3, "Z": 399, "X": 835, "Y": 78 }
// result is ThreeDimensionalPoint; // True
//
// { "$type": "4d", "W": 993, "Z": 427, "X": 508, "Y": 741 }
// result is FourDimensionalPoint; // True
Imports System.Text.Json
Imports System.Text.Json.Serialization
Module Program
Sub Main()
PerformRoundTrip(Of BasePoint)()
PerformRoundTrip(Of ThreeDimensionalPoint)()
PerformRoundTrip(Of FourDimensionalPoint)()
End Sub
Private Sub PerformRoundTrip(Of T As {BasePoint, New})()
Dim json = JsonSerializer.Serialize(Of BasePoint)(New T())
Console.WriteLine(json)
Dim result As BasePoint = JsonSerializer.Deserialize(Of BasePoint)(json)
Console.WriteLine($"result is {GetType(T)}; // {TypeOf result Is T}")
Console.WriteLine()
End Sub
End Module
' Sample output:
' { "X": 649, "Y": 754 }
' result is BasePoint; // True
'
' { "$type": 3, "Z": 247, "X": 814, "Y": 56 }
' result is ThreeDimensionalPoint; // True
'
' { "$type": "4d", "W": 427, "Z": 193, "X": 112, "Y": 935 }
' result is FourDimensionalPoint; // True
Personnaliser le nom du discriminateur de type
Le nom de propriété par défaut du discriminateur de type est $type
. Pour personnaliser le nom de la propriété, utilisez JsonPolymorphicAttribute comme indiqué dans l’exemple suivant :
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$discriminator")]
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: "3d")]
public class BasePoint
{
public int X { get; set; }
public int Y { get; set; }
}
public sealed class ThreeDimensionalPoint : BasePoint
{
public int Z { get; set; }
}
<JsonPolymorphic(TypeDiscriminatorPropertyName:="$discriminator")>
<JsonDerivedType(GetType(ThreeDimensionalPoint), "3d")>
Public Class BasePoint
Public Property X As Integer
Public Property Y As Integer
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
Public Property Z As Integer
End Class
Dans le code précédent, l’attribut JsonPolymorphic
configure le TypeDiscriminatorPropertyName
avec la valeur "$discriminator"
. Une fois le nom du discriminateur de type configuré, l’exemple suivant montre le type ThreeDimensionalPoint
sérialisé au format JSON :
BasePoint point = new ThreeDimensionalPoint { X = 1, Y = 2, Z = 3 };
var json = JsonSerializer.Serialize<BasePoint>(point);
Console.WriteLine(json);
// Sample output:
// { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Dim point As BasePoint = New ThreeDimensionalPoint With { .X = 1, .Y = 2, .Z = 3 }
Dim json As String = JsonSerializer.Serialize(Of BasePoint)(point)
Console.WriteLine(json)
' Sample output:
' { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Conseil
Évitez d’utiliser JsonPolymorphicAttribute.TypeDiscriminatorPropertyName s’il est en conflit avec une propriété dans votre hiérarchie de types.
Gérer des types dérivés inconnus
Pour gérer les types dérivés inconnus, vous devez choisir une telle prise en charge au moyen d’une annotation sur le type de base. Prenez en considération la hiérarchie de types suivante :
[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
public int X { get; set; }
public int Y { get; set; }
}
public class ThreeDimensionalPoint : BasePoint
{
public int Z { get; set; }
}
public class FourDimensionalPoint : ThreeDimensionalPoint
{
public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
Public Property X As Integer
Public Property Y As Integer
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
Public Property Z As Integer
End Class
Public NotInheritable Class FourDimensionalPoint
Inherits ThreeDimensionalPoint
Public Property W As Integer
End Class
Étant donné que la configuration n’accepte pas explicitement la prise en charge de FourDimensionalPoint
, la tentative de sérialisation des instances de FourDimensionalPoint
comme BasePoint
entraîne une exception de runtime :
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
Vous pouvez changer le comportement par défaut en utilisant l’enum JsonUnknownDerivedTypeHandling, qui peut être spécifié comme suit :
[JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
public int X { get; set; }
public int Y { get; set; }
}
public class ThreeDimensionalPoint : BasePoint
{
public int Z { get; set; }
}
public class FourDimensionalPoint : ThreeDimensionalPoint
{
public int W { get; set; }
}
<JsonPolymorphic(
UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToBaseType)>
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
Public Property X As Integer
Public Property Y As Integer
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
Public Property Z As Integer
End Class
Public NotInheritable Class FourDimensionalPoint
Inherits ThreeDimensionalPoint
Public Property W As Integer
End Class
Au lieu de revenir au type de base, vous pouvez utiliser le paramètre FallBackToNearestAncestor
pour revenir au contrat du type dérivé déclaré le plus proche :
[JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint))]
public interface IPoint { }
public class BasePoint : IPoint { }
public class ThreeDimensionalPoint : BasePoint { }
<JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint)>
Public Interface IPoint
End Interface
Public Class BasePoint
Inherits IPoint
End Class
Public Class ThreeDimensionalPoint
Inherits BasePoint
End Class
Avec une configuration comme dans l’exemple précédent, le type ThreeDimensionalPoint
est sérialisé comme BasePoint
:
// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())
Cependant, revenir à l’ancêtre le plus proche admet la possibilité d’une ambiguïté « diamant ». Prenez en considération la hiérarchie de types suivante comme exemple :
[JsonPolymorphic(
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint))]
[JsonDerivedType(typeof(IPointWithTimeSeries))]
public interface IPoint { }
public interface IPointWithTimeSeries : IPoint { }
public class BasePoint : IPoint { }
public class BasePointWithTimeSeries : BasePoint, IPointWithTimeSeries { }
<JsonPolymorphic(
UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint))>
<JsonDerivedType(GetType(IPointWithTimeSeries))>
Public Interface IPoint
End Interface
Public Interface IPointWithTimeSeries
Inherits IPoint
End Interface
Public Class BasePoint
Implements IPoint
End Class
Public Class BasePointWithTimeSeries
Inherits BasePoint
Implements IPointWithTimeSeries
End Class
Dans ce cas, le type BasePointWithTimeSeries
peut être sérialisé comme BasePoint
ou IPointWithTimeSeries
parce qu’ils sont tous deux des ancêtres directs. Cette ambiguïté entraîne la levée de NotSupportedException lors de la tentative de sérialisation d’une instance de BasePointWithTimeSeries
comme IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Configurer le polymorphisme avec le modèle de contrat
Pour les cas d’usage où les annotations d’attribut sont impraticables ou impossibles (par exemple, les grands modèles de domaine, les hiérarchies inter-assemblys ou les hiérarchies dans des dépendances tierces), pour configurer le polymorphisme, utilisez le modèle de contrat. Le modèle de contrat est un ensemble d’API qui peuvent être utilisées pour configurer le polymorphisme dans une hiérarchie de types en créant une sous-classe DefaultJsonTypeInfoResolver personnalisée qui fournit dynamiquement une configuration polymorphe par type, comme illustré dans l’exemple suivant :
public class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
Type basePointType = typeof(BasePoint);
if (jsonTypeInfo.Type == basePointType)
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
{
TypeDiscriminatorPropertyName = "$point-type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
DerivedTypes =
{
new JsonDerivedType(typeof(ThreeDimensionalPoint), "3d"),
new JsonDerivedType(typeof(FourDimensionalPoint), "4d")
}
};
}
return jsonTypeInfo;
}
}
Public Class PolymorphicTypeResolver
Inherits DefaultJsonTypeInfoResolver
Public Overrides Function GetTypeInfo(
ByVal type As Type,
ByVal options As JsonSerializerOptions) As JsonTypeInfo
Dim jsonTypeInfo As JsonTypeInfo = MyBase.GetTypeInfo(type, options)
Dim basePointType As Type = GetType(BasePoint)
If jsonTypeInfo.Type = basePointType Then
jsonTypeInfo.PolymorphismOptions = New JsonPolymorphismOptions With {
.TypeDiscriminatorPropertyName = "$point-type",
.IgnoreUnrecognizedTypeDiscriminators = True,
.UnknownDerivedTypeHandling =
JsonUnknownDerivedTypeHandling.FailSerialization
}
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
New JsonDerivedType(GetType(ThreeDimensionalPoint), "3d"))
jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
New JsonDerivedType(GetType(FourDimensionalPoint), "4d"))
End If
Return jsonTypeInfo
End Function
End Class
Détails supplémentaires sur la sérialisation polymorphe
- La sérialisation polymorphe prend en charge les types dérivés qui ont été explicitement activés via JsonDerivedTypeAttribute. Les types non déclarés entraînent une exception de runtime. Ce comportement peut être modifié en configurant la propriété JsonPolymorphicAttribute.UnknownDerivedTypeHandling.
- La configuration polymorphe spécifiée dans les types dérivés n’est pas héritée par la configuration polymorphe dans les types de base. Le type de base doit être configuré indépendamment.
- Les hiérarchies polymorphes sont prises en charge pour les types
interface
etclass
. - Le polymorphisme utilisant des discriminateurs de type est pris en charge uniquement pour les hiérarchies de types qui utilisent les convertisseurs par défaut pour les objets, les collections et les types de dictionnaires.
- Le polymorphisme est pris en charge dans la génération de source basée sur les métadonnées, mais pas dans la génération de source à chemin rapide.