Serialización de propiedades de clases derivadas con System.Text.Json
En este artículo, aprenderá a serializar las propiedades de las clases derivadas con el espacio de nombres System.Text.Json
.
Serialización de propiedades de clases derivadas
En versiones anteriores a .NET 7, System.Text.Json
no admite la serialización de jerarquías de tipos polimórficos. Por ejemplo, si el tipo de una propiedad se define como interfaz o como clase abstracta, solo se serializan las propiedades definidas en la interfaz o la clase abstracta, aunque el tipo de entorno de ejecución tenga propiedades adicionales. Las excepciones a este comportamiento se explican en esta sección. Para información sobre la compatibilidad con .NET 7, consulte Serialización polimórfica en .NET 7.
Por ejemplo, supongamos que tiene una clase WeatherForecast
y una clase derivada 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
Supongamos también que el argumento de tipo del método Serialize
en tiempo de compilación es 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)
En este escenario, la propiedad WindSpeed
no se serializa aunque el objeto weatherForecast
sea en realidad un objeto WeatherForecastDerived
. Solo se serializan las propiedades de la clase base:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Este comportamiento está diseñado para ayudar a evitar la exposición accidental de datos en un tipo derivado creado en tiempo de ejecución.
Para serializar las propiedades del tipo derivado del ejemplo anterior, use uno de los enfoques siguientes:
Llame a una sobrecarga de Serialize que le permita especificar el tipo en tiempo de ejecución:
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)
Declare el objeto que se va a serializar como
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)
En el escenario de ejemplo anterior, ambos enfoques hacen que la propiedad WindSpeed
se incluya en la salida JSON:
{
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Importante
Estos enfoques proporcionan serialización polimórfica solo para el objeto raíz que se va a serializar, no para las propiedades de dicho objeto raíz.
Puede obtener una serialización polimórfica para objetos de nivel inferior si se definen como tipo object
. Por ejemplo, supongamos que la clase WeatherForecast
tiene una propiedad denominada PreviousForecast
que se puede definir como tipo WeatherForecast
o 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 propiedad PreviousForecast
contiene una instancia de WeatherForecastDerived
:
- La salida JSON de la serialización
WeatherForecastWithPrevious
no incluyeWindSpeed
. - La salida JSON de la serialización
WeatherForecastWithPreviousAsObject
incluyeWindSpeed
.
Para serializar WeatherForecastWithPreviousAsObject
, no es necesario llamar a Serialize<object>
o GetType
porque el objeto raíz no es el que puede ser de un tipo derivado. En el ejemplo de código siguiente no se llama a Serialize<object>
ni GetType
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject1, options)
El código anterior serializa correctamente WeatherForecastWithPreviousAsObject
:
{
"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"
}
}
El mismo enfoque para definir propiedades como object
funciona con interfaces. Supongamos que tiene la interfaz y la implementación siguientes, y quiere serializar una clase con propiedades que contienen instancias de implementación:
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
Cuando se serializa una instancia de Forecasts
, solo Tuesday
muestra la propiedad WindSpeed
, porque Tuesday
se define como 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)
En el ejemplo de código siguiente se muestra el JSON resultante del código anterior:
{
"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
}
}
Nota
Este artículo trata sobre la serialización, no la deserialización. No se admite la deserialización polimórfica en versiones anteriores a .NET 7, pero como solución alternativa, puede escribir un convertidor personalizado, como el ejemplo de Compatibilidad con la deserialización polimórfica. Para más información sobre cómo .NET 7 admite la serialización y deserialización polimórficas, consulte Serialización de propiedades de clases derivadas con System.Text.Json en .NET 7.
A partir de .NET 7, System.Text.Json
admite la serialización y deserialización de la jerarquía de tipos polimórficos con anotaciones de atributos.
Atributo | Descripción |
---|---|
JsonDerivedTypeAttribute | Cuando se coloca en una declaración de tipo, indica que el subtipo especificado debe participar en la serialización polimórfica. También expone la posibilidad de especificar un discriminador de tipos. |
JsonPolymorphicAttribute | Cuando se coloca en una declaración de tipo, indica que el tipo se debe serializar polimórficamente. También expone varias opciones para configurar la serialización y deserialización polimórficas para ese tipo. |
Por ejemplo, supongamos que tiene una clase WeatherForecastBase
y una clase derivada 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
Supongamos también que el argumento de tipo del método Serialize<TValue>
en tiempo de compilación es WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
En este escenario, la propiedad City
no se serializa porque el objeto weatherForecastBase
es en realidad un objeto WeatherForecastWithCity
. Esta configuración permite la serialización polimórfica para WeatherForecastBase
, específicamente cuando el tipo en tiempo de ejecución es WeatherForecastWithCity
:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Aunque se admite el recorrido de ida y vuelta de la carga como WeatherForecastBase
, no se materializará como un tipo en tiempo de ejecución de WeatherForecastWithCity
. En su lugar, se materializará como un tipo en tiempo de ejecución 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
En la sección siguiente se describe cómo agregar metadatos para permitir el recorrido de ida y vuelta del tipo derivado.
Discriminadores de tipos polimórficos
Para habilitar la deserialización polimórfica, debe especificar un discriminador de tipos para la clase derivada:
[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
Con los metadatos agregados, en concreto, el discriminador de tipos, el serializador puede serializar y deserializar la carga como el tipo WeatherForecastWithCity
de su tipo base WeatherForecastBase
. La serialización emitirá JSON junto con los metadatos de discriminador de tipos:
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"
' }
Con el discriminador de tipos, el serializador puede deserializar la carga polimórficamente como 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
Nota:
Por defecto, el discriminador $type
debe colocarse al principio del objeto JSON, agrupado junto con otras propiedades de metadatos como $id
y $ref
. Si está leyendo datos de una API externa que coloca el discriminador $type
en medio del objeto JSON, active JsonSerializerOptions.AllowOutOfOrderMetadataProperties como true
:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Tenga cuidado al habilitar esta marca, ya que podría dar lugar a errores de sobrebúfer (y errores de memoria insuficiente) al realizar la deserialización de streaming de objetos JSON muy grandes.
Combinación y coincidencia de formatos de discriminador de tipos
Los identificadores de discriminador de tipos son válidos en los formatos string
o int
, por lo que lo siguiente es válido:
[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...
' }
Aunque la API admite la combinación y coincidencia de configuraciones de discriminador de tipos, no se recomienda. La recomendación general es usar todos los discriminadores de tipos string
, todos los discriminadores de tipos int
o ningún discriminador. En el ejemplo siguiente se muestra cómo combinar y hacer coincidir las configuraciones de discriminador de tipos:
[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
En el ejemplo anterior, el tipo BasePoint
no tiene un discriminador de tipos, mientras que el tipo ThreeDimensionalPoint
tiene un discriminador de tipos int
y FourDimensionalPoint
tiene un discriminador de tipos string
.
Importante
Para que la serialización polimórfica funcione, el tipo del valor serializado debe ser el del tipo base polimórfico. Esto incluye el uso del tipo base como parámetro de tipo genérico al serializar valores de nivel raíz, como tipo declarado de propiedades serializadas o como elemento de colección en colecciones serializadas.
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
Personalización del nombre del discriminador de tipos
El nombre de propiedad predeterminado del discriminador de tipos es $type
. Para personalizar el nombre de la propiedad, use JsonPolymorphicAttribute como se muestra en el ejemplo siguiente:
[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
En el código anterior, el atributo JsonPolymorphic
configura TypeDiscriminatorPropertyName
en el valor "$discriminator"
. Con el nombre del discriminador de tipos configurado, en el ejemplo siguiente se muestra el tipo ThreeDimensionalPoint
serializado como 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 }
Sugerencia
No use JsonPolymorphicAttribute.TypeDiscriminatorPropertyName si entra en conflicto con una propiedad de la jerarquía de tipos.
Administración de tipos derivados desconocidos
Para administrar tipos derivados desconocidos, debe elegir dicha compatibilidad mediante una anotación en el tipo base. Considere la jerarquía de tipos siguiente:
[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
Dado que la configuración no admite explícitamente la compatibilidad con FourDimensionalPoint
, al intentar serializar instancias de FourDimensionalPoint
como BasePoint
se producirá una excepción en tiempo de ejecución:
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
Puede cambiar el comportamiento predeterminado mediante la enumeración JsonUnknownDerivedTypeHandling, que se puede especificar de la siguiente manera:
[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
En lugar de retroceder al tipo base, puede usar el valor FallBackToNearestAncestor
para retroceder al contrato del tipo derivado declarado más cercano:
[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
Con una configuración como el ejemplo anterior, el tipo ThreeDimensionalPoint
se serializará como BasePoint
:
// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())
Sin embargo, retroceder hasta el antepasado más próximo admite la posibilidad de ambigüedad del "diamante". Considere la siguiente jerarquía de tipos como ejemplo:
[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
En este caso, el tipo BasePointWithTimeSeries
se podría serializar como BasePoint
o IPointWithTimeSeries
dado que ambos son antecesores directos. Esta ambigüedad provocará que se inicie la excepción NotSupportedException al intentar serializar una instancia de BasePointWithTimeSeries
como IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Configuración del polimorfismo con el modelo de contrato
En los casos de uso en los que las anotaciones de atributo no son prácticas o imposibles (como modelos de dominio grandes, jerarquías entre ensamblados o jerarquías en dependencias de terceros), para configurar el polimorfismo use el modelo de contrato. El modelo de contrato es un conjunto de API que se pueden usar para configurar el polimorfismo en una jerarquía de tipos mediante la creación de una subclase DefaultJsonTypeInfoResolver personalizada que proporciona dinámicamente una configuración polimórfica por tipo, como se muestra en el ejemplo siguiente:
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
Detalles adicionales de serialización polimórfica
- La serialización polimórfica admite tipos derivados que se han elegido explícitamente a través de JsonDerivedTypeAttribute. Los tipos no declarados producirán una excepción en tiempo de ejecución. Este comportamiento se puede cambiar configurando la propiedad JsonPolymorphicAttribute.UnknownDerivedTypeHandling.
- La configuración polimórfica en tipos base no hereda la configuración polimórfica especificada en los tipos derivados. El tipo base debe configurarse de forma independiente.
- Se admiten jerarquías polimórficas para los tipos
interface
yclass
. - El polimorfismo mediante discriminadores de tipos solo se admite para jerarquías de tipos que usan los convertidores predeterminados para objetos, colecciones y tipos de diccionario.
- El polimorfismo se admite en la generación de origen basada en metadatos, pero no en la generación de origen de ruta de acceso rápida.