web-dev-qa-db-fra.com

Comment obtenir la valeur d'un objet à l'intérieur d'un objet à l'aide de System.Text.Json dans .net core 3.1

Créé une . Net Core 3.1 Application Web et posté la demande où le modèle demandé ressemble,

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }
    }

Je suis très nouveau dans le noyau 3.1 et j'ai du mal à obtenir la valeur de la propriété Payload. Quelqu'un peut-il m'aider à ce sujet?

En trouvant la solution, j'ai également comparé Newtonsoft et System.Text.Json et j'ai obtenu Error.

En utilisant Newtonsoft je suis capable pour sérialiser et désérialiser un modèle illustré ci-dessous,

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }

        //Problem is here -> TYPE
        public Type PayloadType { get; set; }
    }

mais en utilisant System.Text.Json Je suis pas While serializing got error "System.Text.Json.JsonException: 'Un cycle d'objet possible était détecté qui n'est pas pris en charge. "

Pour tester désérialisation, en quelque sorte créé JSON et essaie de le désérialiser à l'aide de System.Text.Json mais en obtenant une erreur "System.Text.Json.JsonException: 'La valeur JSON n'a pas pu être convertie en System.Type . "

Utilisé System.Text.Json.JsonSerializer, est-ce un problème ou y a-t-il une autre possibilité pour que cela fonctionne?

1
Darshana

Je suis très nouveau dans le noyau 3.1 et j'ai du mal à obtenir la valeur de la propriété Payload. Quelqu'un peut-il m'aider à ce sujet?

Pour les propriétés System.Object, contrairement à Newtonsoft.Json, System.Text.Json Fait not essayez de déduire le type de la charge utile JSON pour les valeurs primitives (telles que true, 12345.67, "hello"). De même, pour les valeurs JSON complexes telles que les objets et les tableaux (tels que {"Name":"hi"} Ou [1, 2, 3]), La propriété d'objet est définie comme un JsonElement encadré qui représente le JSON transmis. Ceci est similaire à la façon dont Newtonsoft.Json Stocke un JObject dans le object property Pour les types complexes. Voir https://docs.Microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1

Comme vous le feriez avec le JObject de Newtonsoft.Json, vous pouvez parcourir et accéder aux valeurs dans le modèle d'objet de document JSON (DOM) à l'aide de JsonElement et appeler des API de conversion pour obtenir des valeurs .NET ( comme GetProperty(String) et GetInt32()).

L'exemple suivant montre comment accéder aux valeurs Payload, une fois que vous avez désérialisé le JSON en un RequestPayload.

private static void ObjectPropertyExample()
{
    using JsonDocument doc = JsonDocument.Parse("{\"Name\":\"Darshana\"}");
    JsonElement payload = doc.RootElement.Clone();

    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = payload
    };

    string json = JsonSerializer.Serialize(requestPayload);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":{"Name":"Darshana"}}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json);

    JsonElement element = (JsonElement)roundtrip.Payload;
    string name = element.GetProperty("Name").GetString();
    Assert.Equal("Darshana", name);
}

En trouvant la solution, j'ai également comparé Newtonsoft et System.Text.Json et j'ai obtenu une erreur.

Même si la sérialisation d'une classe contenant une propriété System.Type Est acceptable, elle n'est pas recommandée, en particulier pour les applications Web (il y a problèmes potentiels avec la divulgation d'informations).

D'autre part, désérialisation JSON dans une classe qui contient une propriété Type, en particulier en utilisant Type.GetType(untrusted-string-input) est certainement pas recommandé car il introduit des vulnérabilités de sécurité potentielles dans votre application.

C'est pourquoi le System.Text.Json Intégré intentionnellement ne prend pas en charge la sérialisation/désérialisation des propriétés Type. Le message d'exception que vous voyez lors de la sérialisation est dû au fait que Type contient un cycle dans son graphique d'objets et que JsonSerializer ne gère actuellement pas les cycles. Si vous ne vous souciez que de la sérialisation (c'est-à-dire de l'écriture) de la classe en JSON, vous pouvez créer votre propre JsonConverter<Type> Pour en ajouter le support (pour produire le même JSON que Newtonsoft.Json). Quelque chose comme ce qui suit fonctionnera:

private class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        // Caution: Deserialization of type instances like this 
        // is not recommended and should be avoided
        // since it can lead to potential security issues.

        // If you really want this supported (for instance if the JSON input is trusted):
        // string assemblyQualifiedName = reader.GetString();
        // return Type.GetType(assemblyQualifiedName);
        throw new NotSupportedException();
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        // Use this with caution, since you are disclosing type information.
        writer.WriteStringValue(value.AssemblyQualifiedName);
    }
}

Vous pouvez ensuite ajouter le convertisseur personnalisé dans les options et le transmettre à JsonSerializer.Serialize:

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomJsonConverterForType());

Considérez réévaluer pourquoi vous avez besoin de la propriété Type sur votre classe qui est sérialisée et désérialisée pour commencer.

Voir https://github.com/dotnet/corefx/issues/42712 pour plus d'informations et un contexte sur les raisons pour lesquelles vous ne devriez pas désérialiser les classes contenant Type propriétés en utilisant Type.GetType(string).

Voici plus d'informations sur la façon d'écrire un convertisseur personnalisé: https://docs.Microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to

Une approche qui peut fonctionner de manière plus sûre (et par conséquent ce que je recommanderais ) est d'utiliser une énumération de discriminateur de type qui contient la liste des types statiquement connus que vous expect et supporte et crée explicitement ces types en fonction des valeurs d'énumération dans JsonConverter<Type>.

Voici un exemple de ce à quoi cela ressemblerait:

// Let's assume these are the list of types we expect for the `Type` property
public class ExpectedType1 { }
public class ExpectedType2 { }
public class ExpectedType3 { }

public class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();

        Type type = typeDiscriminator switch
        {
            TypeDiscriminator.ExpectedType1 => typeof(ExpectedType1),
            TypeDiscriminator.ExpectedType2 => typeof(ExpectedType2),
            TypeDiscriminator.ExpectedType3 => typeof(ExpectedType3),
            _ => throw new NotSupportedException(),
        };
        return type;
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        if (value == typeof(ExpectedType1))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType1);
        }
        else if (value == typeof(ExpectedType2))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType2);
        }
        else if (value == typeof(ExpectedType3))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType3);
        }
        else
        {
            throw new NotSupportedException();
        }
    }

    // Used to map supported types to an integer and vice versa.
    private enum TypeDiscriminator
    {
        ExpectedType1 = 1,
        ExpectedType2 = 2,
        ExpectedType3 = 3,
    }
}

private static void TypeConverterExample()
{
    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = "payload",
        PayloadType = typeof(ExpectedType1)
    };

    var options = new JsonSerializerOptions()
    {
        Converters = { new CustomJsonConverterForType() }
    };

    string json = JsonSerializer.Serialize(requestPayload, options);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":"payload","PayloadType":1}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json, options);
    Assert.Equal(typeof(ExpectedType1), roundtrip.PayloadType);
}
6
ahsonkhan