Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Neste artigo, você aprenderá a serializar propriedades de classes derivadas com o System.Text.Json namespace.
Serializar propriedades de classes derivadas
A partir do .NET 7, System.Text.Json oferece suporte à serialização e desserialização de hierarquia de tipos polimórficos com anotações de atributo.
| Atributo | Description |
|---|---|
| JsonDerivedTypeAttribute | Quando colocado em uma declaração de tipo, indica que o subtipo especificado deve ser optado pela serialização polimórfica. Também expõe a capacidade de especificar um discriminador de tipo. |
| JsonPolymorphicAttribute | Quando colocado em uma declaração de tipo, indica que o tipo deve ser serializado polimorficamente. Ele também expõe várias opções para configurar a serialização polimórfica e a desserialização para esse tipo. |
Por exemplo, suponha que você tenha uma WeatherForecastBase classe e uma classe WeatherForecastWithCityderivada:
[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
E suponha que o Serialize<TValue> argumento type do método em tempo de compilação é 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 City propriedade é serializada porque o weatherForecastBase objeto é realmente um WeatherForecastWithCity objeto. Essa configuração permite a serialização polimórfica para WeatherForecastBase, especificamente quando o tipo de tempo de execução é WeatherForecastWithCity:
{
"City": "Milwaukee",
"Date": "2022-09-26T00:00:00-05:00",
"TemperatureCelsius": 15,
"Summary": "Cool"
}
Embora o retorno da carga útil como WeatherForecastBase seja suportado, ela não se concretizará como um tipo de execução em tempo de execução de WeatherForecastWithCity. Em vez disso, irá materializar-se 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 a ida e volta do tipo derivado.
Discriminadores de tipo polimórficos
Para habilitar a desserialização polimórfica, você deve 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 a carga como o WeatherForecastWithCity tipo de seu tipo WeatherForecastBasebase. A serialização emite JSON juntamente 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 a carga polimorficamente 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 padrão, o $type discriminador 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 $type discriminador 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 ele pode resultar em buffer excessivo (e falhas de falta de memória) ao executar a desserialização de streaming de objetos JSON muito grandes.
Misturar e combinar formatos discriminadores de tipo
Os identificadores discriminadores de tipo são válidos em qualquer uma das stringint formas, pelo que 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 suporte configurações discriminadoras de tipo de mistura e correspondência, ela não é recomendada. A recomendação geral é usar todos os string tipos de discriminadores, todos os int tipos de discriminadores ou nenhum discriminador. O exemplo a seguir mostra como misturar e corresponder 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 BasePoint tipo não tem um discriminador de tipo, enquanto o ThreeDimensionalPoint tipo tem um discriminador de int tipo e o FourDimensionalPoint tem um string discriminador de tipo.
Importante
Para que a serialização polimórfica funcione, o tipo do valor serializado deve ser o do tipo 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 para o discriminador de tipo é $type. Para personalizar o nome da propriedade, use o JsonPolymorphicAttribute como mostrado no exemplo a seguir:
[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 JsonPolymorphic atributo configura o TypeDiscriminatorPropertyName para o "$discriminator" valor. Com o nome do discriminador de tipo configurado, o exemplo a seguir mostra o tipo serializado ThreeDimensionalPoint 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 }
Gorjeta
Evite usar uma JsonPolymorphicAttribute.TypeDiscriminatorPropertyName propriedade que entre em conflito com uma propriedade na hierarquia de tipos.
Manipular tipos derivados desconhecidos
Para lidar com tipos derivados desconhecidos, você deve optar por esse suporte usando uma anotação no tipo base. Considere a seguinte hierarquia de tipo:
[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 opta explicitamente por suporte para FourDimensionalPoint, tentar serializar instâncias de FourDimensionalPoint como BasePoint resultará numa 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 o JsonUnknownDerivedTypeHandling enum, que pode ser especificado 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 de voltar para o tipo base, você pode usar a FallBackToNearestAncestor configuração para retornar ao 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 será serializado ThreeDimensionalPoint 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, voltar para o ancestral mais próximo admite a possibilidade de ambiguidade "diamante". Considere a seguinte hierarquia de tipo 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
Neste caso, o BasePointWithTimeSeries tipo pode ser serializado como um ou uma BasePointIPointWithTimeSeries vez que ambos são ancestrais diretos. Essa ambiguidade fará com que o NotSupportedException seja lançado 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 polimorfismo com o modelo de contrato
Para casos de uso em que as anotações de atributo são impraticáveis ou impossíveis (como modelos de domínio grande, hierarquias de montagem cruzada ou hierarquias em dependências de terceiros), para configurar o polimorfismo, use o modelo de contrato. O modelo de contrato é um conjunto de APIs que pode ser usado para configurar o polimorfismo em uma hierarquia de tipos, criando uma subclasse personalizada DefaultJsonTypeInfoResolver que fornece dinamicamente configuração polimórfica por tipo, conforme mostrado no exemplo a seguir:
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 de serialização polimórfica
- A serialização polimórfica suporta tipos derivados que foram explicitamente aceitos por meio do JsonDerivedTypeAttribute. Tipos não declarados resultarão numa exceção em tempo de execução. O comportamento pode ser alterado configurando a JsonPolymorphicAttribute.UnknownDerivedTypeHandling propriedade.
- A configuração polimórfica especificada em tipos derivados não é herdada pela configuração polimórfica em tipos base. O tipo base deve ser configurado independentemente.
- Hierarquias polimórficas são suportadas para ambos os
interfacetipos eclasstipos. - O polimorfismo usando discriminadores de tipo só é suportado para hierarquias de tipo que usam os conversores padrão para objetos, coleções e tipos de dicionário.
- O polimorfismo é suportado na geração de origem baseada em metadados, mas não na geração de origem de caminho rápido.