Como serializar propriedades de classes derivadas com System.Text.Json
Neste artigo, você aprende a serializar propriedades de classes derivadas com o namespace System.Text.Json
.
Serializar propriedades de classes derivadas
Em versões anteriores ao .NET 7, System.Text.Json
não dá suporte à serialização de hierarquias de tipo polimórfico. Por exemplo, se o tipo de uma propriedade for uma interface ou uma classe abstrata, somente as propriedades definidas na interface ou classe abstrata serão serializadas, mesmo que o tipo de runtime tenha propriedades adicionais. As exceções a esse comportamento são explicadas nesta seção. Para obter informações sobre o suporte no .NET 7, consulte Serialização polimórfica no .NET 7.
Por exemplo, suponha que você tenha uma classe WeatherForecast
e uma classe 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
Suponha também que o argumento de tipo do método Serialize
em tempo de compilação seja 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)
Nesse cenário, a propriedade WindSpeed
não será serializada mesmo se o objeto weatherForecast
for um objeto WeatherForecastDerived
. Somente as propriedades da classe base são serializadas:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Esse comportamento se destina a ajudar a evitar a exposição acidental de dados em um tipo criado por runtime derivado.
Para serializar as propriedades do tipo derivado no exemplo anterior, use uma das seguintes abordagens:
Chamar uma sobrecarga de Serialize permite especificar o tipo no tempo de execução:
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 o objeto a ser serializado 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)
No cenário de exemplo anterior, ambas as abordagens fazem com que a propriedade WindSpeed
seja incluída na saída JSON:
{
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
Importante
Essas abordagens fornecem serialização polimórfica apenas para que o objeto raiz seja serializado, não para propriedades desse objeto raiz.
Você pode obter serialização polimórfica para objetos de nível inferior se defini-los como o tipo object
. Por exemplo, suponha que sua classe WeatherForecast
tenha uma propriedade nomeada PreviousForecast
que pode ser definida como tipo 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
Se a propriedade PreviousForecast
contiver uma instância de WeatherForecastDerived
:
- A saída JSON da serialização
WeatherForecastWithPrevious
não incluiWindSpeed
. - A saída JSON da serialização
WeatherForecastWithPreviousAsObject
incluiWindSpeed
.
Para serializar WeatherForecastWithPreviousAsObject
, não é necessário chamar Serialize<object>
ou GetType
porque o objeto raiz não é aquele que pode ser de um tipo derivado. O seguinte exemplo de código não chama 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)
O código anterior serializa WeatherForecastWithPreviousAsObject
corretamente:
{
"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"
}
}
A mesma abordagem de definir propriedades como object
funciona com interfaces. Suponha que você tenha a seguinte interface e implementação e queira serializar uma classe com propriedades que contenham instâncias de implementação:
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
Quando você serializa uma instância de Forecasts
, apenas Tuesday
mostra a propriedade WindSpeed
, pois Tuesday
é definido 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)
O seguinte exemplo mostra o JSON que resulta do 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
}
}
Observação
Este artigo é sobre serialização, não desserialização. Não há suporte para desserialização polimórfica em versões anteriores ao .NET 7, porém, como solução alternativa, você pode escrever um conversor personalizado, como o exemplo em Suporte à desserialização polimórfica. Para obter mais informações sobre como o .NET 7 dá suporte à serialização e à desserialização polimórfica, consulte Como serializar propriedades de classes derivadas com System.Text.Json no .NET 7.
Começando no .NET 7, System.Text.Json
dá suporte à serialização e à desserialização de hierarquia de tipo polimórfico com anotações de atributo.
Atributo | Descrição |
---|---|
JsonDerivedTypeAttribute | Quando colocado em uma declaração de tipo, indica que o subtipo especificado deve ser aceito pela serialização polimórfica. Também expõe a capacidade de especificar um descriminador de tipo. |
JsonPolymorphicAttribute | Quando colocado em uma declaração de tipo, indica que o tipo deve ser serializado polimorficamente. Também expõe várias opções para configurar a serialização e a desserialização polimórfica para esse tipo. |
Por exemplo, suponha que você tenha uma classe WeatherForecastBase
e uma classe 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
Suponha também que o argumento de tipo do método Serialize<TValue>
em tempo de compilação seja WeatherForecastBase
:
options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
.WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)
Nesse cenário, a propriedade City
é serializada porque o objeto weatherForecastBase
é, na verdade, um objeto WeatherForecastWithCity
. Essa configuração habilita a serialização polimórfica para WeatherForecastBase
, especificamente quando o tipo de runtime é WeatherForecastWithCity
:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Embora haja suporte para viagens de ia e volta do conteúdo como WeatherForecastBase
, isso não se materializará como um tipo de tempo de execução de WeatherForecastWithCity
. Em vez disso, se materializará como um tipo de tempo de execução 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
A seção a seguir descreve como adicionar metadados para habilitar viagens de ida e volta do tipo derivado.
Discriminadores de tipo polimórfico
Para habilitar a desserialização polimórfica, você precisa especificar um discriminador de tipo para a classe 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
Com os metadados adicionados, especificamente o discriminador de tipo, o serializador pode serializar e desserializar o conteúdo como o tipo WeatherForecastWithCity
de seu tipo base WeatherForecastBase
. A serialização emite o JSON com os metadados do discriminador de tipo:
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"
' }
Com o discriminador de tipo, o serializador pode desserializar o conteúdo de maneira polimórfica 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
Observação
Por padrão, o discriminador $type
deve ser colocado no início do objeto JSON, agrupado com outras propriedades de metadados, como $id
e $ref
. Se você estiver lendo dados de uma API externa que coloca o discriminador $type
no meio do objeto JSON, defina JsonSerializerOptions.AllowOutOfOrderMetadataProperties como true
:
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);
Tenha cuidado ao habilitar esse sinalizador, pois isso pode resultar em excesso de buffer (e falhas de falta de memória) ao executar a desserialização de streaming de objetos JSON muito grandes.
Combinação de formatos de discriminador de tipo
Identificadores de discriminador de tipo são válidos em formulários string
ou int
, portanto, o seguinte é 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...
' }
Embora a API dê suporte à combinação de configurações de discriminador de tipo, isso não é recomendável. A recomendação geral é usar apenas discriminadores de tipo string
, apenas discriminadores de tipo int
ou nenhum discriminador. O seguinte exemplo mostra como combinar configurações de discriminador de tipo:
[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
No exemplo anterior, o tipo BasePoint
não tem um discriminador de tipo, enquanto o tipo ThreeDimensionalPoint
tem um discriminador de tipo int
e o FourDimensionalPoint
tem um discriminador de tipo string
.
Importante
Para que a serialização polimórfica funcione, o tipo do valor serializado deve ser de base polimórfica. Isso inclui o uso do tipo base como o parâmetro de tipo genérico ao serializar valores de nível raiz, como o tipo declarado de propriedades serializadas ou como o elemento de coleção em coleções 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
Personalizar o nome do discriminador de tipo
O nome da propriedade padrão do discriminador de tipo é $type
. Para personalizar o nome da propriedade, use JsonPolymorphicAttribute conforme mostrado no seguinte exemplo:
[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
No código anterior, o atributo JsonPolymorphic
configura o TypeDiscriminatorPropertyName
para o valor "$discriminator"
. Com o nome do discriminador de tipo configurado, o seguinte exemplo mostra o 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 }
Dica
Evite usar um JsonPolymorphicAttribute.TypeDiscriminatorPropertyName que entrar em conflito com uma propriedade em sua hierarquia de tipos.
Manipular tipos derivados desconhecidos
Para lidar com tipos derivados desconhecidos, você precisa aceitar esse suporte usando uma anotação no tipo base. Considere a seguinte hierarquia de tipos:
[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
Como a configuração não aceita explicitamente o suporte para FourDimensionalPoint
, tentar serializar instâncias de FourDimensionalPoint
como BasePoint
resultará em uma exceção em tempo de execução:
JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException
Você pode alterar o comportamento padrão usando a enumeração JsonUnknownDerivedTypeHandling, que pode ser especificada da seguinte maneira:
[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
Em vez fazer fallback para o tipo base, você pode usar a configuração FallBackToNearestAncestor
para fazer fallback para o contrato do tipo derivado declarado mais próximo:
[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
Com uma configuração como o exemplo anterior, o tipo ThreeDimensionalPoint
será serializado 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())
No entanto, fazer fallback para o ancestral mais próximo admite a possibilidade de uma ambiguidade "diamante". Considere a seguinte hierarquia de tipos como exemplo:
[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
Nesse caso, o tipo BasePointWithTimeSeries
pode ser serializado como BasePoint
ou IPointWithTimeSeries
, pois ambos são ancestrais diretos. Essa ambiguidade fará com que NotSupportedException seja gerado ao tentar serializar uma instância de BasePointWithTimeSeries
como IPoint
.
// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())
Configurar o polimorfismo com o modelo de contrato
Para casos de uso em que anotações de atributo são impraticáveis ou impossíveis (como modelos de domínio grandes, hierarquias entre assemblies ou hierarquias em dependências de terceiros) use o modelo de contrato para configurar o polimorfismo. O modelo de contrato é um conjunto de APIs que podem ser usadas para configurar o polimorfismo em uma hierarquia de tipos criando uma subclasse personalizada DefaultJsonTypeInfoResolver que fornece dinamicamente a configuração polimórfica por tipo, conforme mostrado no seguinte exemplo:
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
Detalhes adicionais da serialização polimórfica
- A serialização polimórfica dá suporte a tipos derivados que foram aceitos explicitamente por meio do JsonDerivedTypeAttribute. Tipos não declarados resultarão em uma exceção em tempo de execução. O comportamento pode ser alterado configurando a propriedade JsonPolymorphicAttribute.UnknownDerivedTypeHandling.
- A configuração polimórfica especificada nos tipos derivados não é herdada pela configuração polimórfica nos tipos base. O tipo base deve ser configurado independentemente.
- As hierarquias polimórficas têm suporte para os tipos
interface
eclass
. - O polimorfismo usando discriminadores de tipo só tem suporte para hierarquias de tipos que usam conversores padrão para objetos, coleções e tipos de dicionário.
- O polimorfismo tem suporte na geração de origem baseada em metadados, mas não na geração de origem de caminho rápido.
Confira também
- Visão geral de System.Text.Json
- Como serializar e desserializar JSON