Saltar al contenido

¿Es posible la deserialización polimórfica en System.Text.Json?

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *