Hola, hallamos la respuesta a lo que estabas buscando, continúa leyendo y la hallarás un poco más abajo.
Solución:
¿Es posible la deserialización polimórfica en System.Text.Json?
La respuesta es sí y no, dependiendo de lo que quieras decir con “posible”.
Hay no deserialización polimórfica (equivalente a Newtonsoft.Json TypeNameHandling
) apoyo incorporado para System.Text.Json
. Esto se debe a que al leer el nombre del tipo .NET especificado como string dentro de la carga útil JSON (como $type
propiedad de metadatos) para crear sus objetos es no recomendado ya que presenta posibles problemas de seguridad (consulte https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492 para obtener más información).
Permitir que la carga útil especifique su propio tipo de información es una fuente común de vulnerabilidades en las aplicaciones web.
Sin embargo, hay es una forma de agregar su propio soporte para la deserialización polimórfica mediante la creación de un JsonConverter
, entonces en ese sentido, es posible.
Los documentos muestran un ejemplo de cómo hacerlo usando un discriminador de tipo propiedad: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization
Veamos un ejemplo.
Digamos que tiene una clase base y un par de clases derivadas:
public class BaseClass
public int Int get; set;
public class DerivedA : BaseClass
public string Str get; set;
public class DerivedB : BaseClass
public bool Bool get; set;
Puede crear lo siguiente JsonConverter
que escribe el discriminador de tipos mientras se serializa y lo lee para averiguar qué tipo deserializar. Puede registrar ese convertidor en el JsonSerializerOptions
.
public class BaseClassConverter : JsonConverter
private enum TypeDiscriminator
BaseClass = 0,
DerivedA = 1,
DerivedB = 2
public override bool CanConvert(Type type)
return typeof(BaseClass).IsAssignableFrom(type);
public override BaseClass Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
if (!reader.Read()
public override void Write(
Utf8JsonWriter writer,
BaseClass value,
JsonSerializerOptions options)
writer.WriteStartObject();
if (value is DerivedA derivedA)
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedA);
writer.WritePropertyName("TypeValue");
JsonSerializer.Serialize(writer, derivedA);
else if (value is DerivedB derivedB)
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedB);
writer.WritePropertyName("TypeValue");
JsonSerializer.Serialize(writer, derivedB);
else
throw new NotSupportedException();
writer.WriteEndObject();
Así es como se vería la serialización y deserialización (incluida la comparación con Newtonsoft.Json):
private static void PolymorphicSupportComparison()
var objects = new List new DerivedA(), new DerivedB() ;
// Using: System.Text.Json
var options = new JsonSerializerOptions
Converters = new BaseClassConverter() ,
WriteIndented = true
;
string jsonString = JsonSerializer.Serialize(objects, options);
Console.WriteLine(jsonString);
/*
[
"TypeDiscriminator": 1,
"TypeValue":
"Str": null,
"Int": 0
,
"TypeDiscriminator": 2,
"TypeValue":
"Bool": false,
"Int": 0
]
*/
var roundTrip = JsonSerializer.Deserialize>(jsonString, options);
// Using: Newtonsoft.Json
var settings = new Newtonsoft.Json.JsonSerializerSettings
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
Formatting = Newtonsoft.Json.Formatting.Indented
;
jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(objects, settings);
Console.WriteLine(jsonString);
/*
[
"$type": "PolymorphicSerialization.DerivedA, PolymorphicSerialization",
"Str": null,
"Int": 0
,
"$type": "PolymorphicSerialization.DerivedB, PolymorphicSerialization",
"Bool": false,
"Int": 0
]
*/
var originalList = JsonConvert.DeserializeObject>(jsonString, settings);
Debug.Assert(originalList[0].GetType() == roundTrip[0].GetType());
Aquí hay otra pregunta de StackOverflow que muestra cómo admitir la deserialización polimórfica con interfaces (en lugar de clases abstractas), pero se aplicaría una solución similar para cualquier polimorfismo: ¿Existe una forma simple de serializar / deserializar manualmente objetos secundarios en un convertidor personalizado en System.Text? .Json?
Terminé con esa solución. Es ligero y bastante genérico para mí.
El convertidor discriminador de tipos
public class TypeDiscriminatorConverter : JsonConverter where T : ITypeDiscriminator
private readonly IEnumerable _types;
public TypeDiscriminatorConverter()
var type = typeof(T);
_types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
.ToList();
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
if (!jsonDocument.RootElement.TryGetProperty(nameof(ITypeDiscriminator.TypeDiscriminator), out var typeProperty))
throw new JsonException();
var type = _types.FirstOrDefault(x => x.Name == typeProperty.GetString());
if (type == null)
throw new JsonException();
var jsonObject = jsonDocument.RootElement.GetRawText();
var result = (T) JsonSerializer.Deserialize(jsonObject, type);
return result;
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
JsonSerializer.Serialize(writer, (object)value, options);
La interfaz
public interface ITypeDiscriminator
string TypeDiscriminator get;
Y los modelos de ejemplo
public interface ISurveyStepResult : ITypeDiscriminator
string Id get; set;
public class BoolStepResult : ISurveyStepResult
public string Id get; set;
public string TypeDiscriminator => nameof(BoolStepResult);
public bool Value get; set;
public class TextStepResult : ISurveyStepResult
public string Id get; set;
public string TypeDiscriminator => nameof(TextStepResult);
public string Value get; set;
public class StarsStepResult : ISurveyStepResult
public string Id get; set;
public string TypeDiscriminator => nameof(StarsStepResult);
public int Value get; set;
Y aquí está el método de prueba.
public void SerializeAndDeserializeTest()
var surveyResult = new SurveyResultModel()
Id = "id",
SurveyId = "surveyId",
Steps = new List()
new BoolStepResult() Id = "1", Value = true,
new TextStepResult() Id = "2", Value = "some text",
new StarsStepResult() Id = "3", Value = 5,
;
var jsonSerializerOptions = new JsonSerializerOptions()
Converters = new TypeDiscriminatorConverter(),
WriteIndented = true
;
var result = JsonSerializer.Serialize(surveyResult, jsonSerializerOptions);
var back = JsonSerializer.Deserialize(result, jsonSerializerOptions);
var result2 = JsonSerializer.Serialize(back, jsonSerializerOptions);
Assert.IsTrue(back.Steps.Count == 3
&& back.Steps.Any(x => x is BoolStepResult)
&& back.Steps.Any(x => x is TextStepResult)
&& back.Steps.Any(x => x is StarsStepResult)
);
Assert.AreEqual(result2, result);
Pruebe esta biblioteca que escribí como una extensión de System.Text.Json para ofrecer polimorfismo: https://github.com/dahomey-technologies/Dahomey.Json
Si el tipo real de una instancia de referencia difiere del tipo declarado, la propiedad discriminadora se agregará automáticamente al json de salida:
public class WeatherForecast
public DateTimeOffset Date get; set;
public int TemperatureCelsius get; set;
public string Summary get; set;
public class WeatherForecastDerived : WeatherForecast
public int WindSpeed get; set;
Las clases heredadas deben registrarse manualmente en el registro de la convención del discriminador para que el marco sepa sobre la asignación entre un valor discriminador y un tipo:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry();
registry.RegisterType();
string json = JsonSerializer.Serialize(weatherForecastDerived, options);
Resultado:
"$type": "Tests.WeatherForecastDerived, Tests",
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"WindSpeed": 35
Te mostramos comentarios y valoraciones
Puedes añadir valor a nuestra información cooperando tu experiencia en las referencias.