Runtime: Supporte le DOM "dynamique" et accessible en écriture

Créé le 29 mai 2019  ·  47Commentaires  ·  Source: dotnet/runtime

JsonSerializer.Parse(String, Type, JsonSerializerOptions) charge le type de retour dynamique ExpandoObject ?

Quelque chose comme ça:
dynamic p = JsonSerializer.Parse(json, typeof(ExpandoObject));

area-System.Text.Json enhancement json-functionality-doc

Commentaire le plus utile

J'ai également un cas d'utilisation pour cela - je veux juste appeler une API REST avec HttpClient et récupérer une seule propriété de la réponse. Je ne veux pas créer une classe dédiée juste pour analyser la réponse... Avec JSON.NET, je pourrais simplement utiliser "dynamic" et accéder à la propriété de mon choix.

Tous les 47 commentaires

Non, cette fonctionnalité n'est pas prise en charge à ce stade, mais nous devrions en tenir compte pour vNext. Avez-vous un exemple d'utilisation pour motiver la demande de fonctionnalité ?

Marquage comme futur.

System.NotSupportedException : The collection type 'System.Dynamic.ExpandoObject' is not supported.

@ahsonkhan GraphQL est un bon exemple.

La spécification recommande JSON mais elle n'est liée à aucune sérialisation spécifique dans la réponse.
Ceci implique que le champ « data » de la réponse est du genre : dynamique. Comme cela ne peut pas être déduit.
Sans ExpandoObject, la désérialisation rend dynamique un type de création JSON. Ainsi, l'accès à ces "données" dynamiques abstraites doit être fait en sachant qu'en fait, cette dynamique est un JToken.

Avec ExpandoObject je pense qu'on pourrait imposer l'accès aux "données" dynamiques comme un objet commun

@ahsonkhan un autre exemple est le service de configuration dans notre projet. Il expose une collection comme les points de terminaison REST, qui créent des collections dans MongoDB (ce n'est pas seulement un wrapper REST factice, et les collections de repos et la collection mongo n'ont pas de mappage 1-1 exact, il affirme également certaines règles).

Donc, dans notre projet, nous avons besoin d'un support dynamique/ExpandoObject.

Nous l'utilisons également dans d'autres microservices.

Nous avons également rencontré cette limitation. Notre cas d'utilisation consiste à construire progressivement un objet dynamique avant la sérialisation json. Je suis revenu au sérialiseur Json.NET plus mature.

salut les gars

quelle est la promenade pour l'instant?
Puis-je configurer lequel utiliser ?
@SidShetye, vous avez dit que quelque chose était revenu à une version plus mature, pouvez-vous expliquer s'il vous plaît ?

@MickeyReznikov , votre question a-t-elle été répondue ? Je crois que @SidShetye signifiait revenir à l'utilisation de Newtonsoft.Json pour sérialiser des objets dynamiques, car la bibliothèque intégrée (System.Text.Json) ne les prend pas encore en charge. Dans le cas des applications asp.net, vous pouvez le configurer sur AddNewtonsoftJson en arrière.

Voir https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#jsonnet -support

J'ai également un cas d'utilisation pour cela - je veux juste appeler une API REST avec HttpClient et récupérer une seule propriété de la réponse. Je ne veux pas créer une classe dédiée juste pour analyser la réponse... Avec JSON.NET, je pourrais simplement utiliser "dynamic" et accéder à la propriété de mon choix.

De https://github.com/dotnet/corefx/issues/41472 par @ghost1372 :

salut j'espère que c'est un bon endroit pour poser cette question
Il semble que nous ne pouvons pas désérialiser les objets dynamiques
j'ai utilisé ce code mais n'a pas fonctionné, y a-t-il un moyen de le faire?

var objList = System.Text.Json.JsonSerializer.Deserialize<List<dynamic>>(json);

Dans nos nombreuses applications, nous contrôlons les champs de données des procédures stockées et rendons dynamiquement les pages Liste et Liste de recherche avec jquery.jtable

Sans la prise en charge intégrée de JsonSerializer pour ExpandoObject, nous ne pouvons pas utiliser la sérialisation Json intégrée au noyau dotnet

Nous partageons le même cas d'utilisation que celui déjà mentionné par @estiller et @SidShetye
Entre-temps, nous avons dû revenir à Json.NET.

Est-il possible d'avoir un jalon plus proche du _maintenant_ que du futur ? ??

ExpandoObject est dans la BCL depuis très longtemps

Existe-t-il une alternative pour ExpandoObject ?

@fatihyildizhan Non, il n'y a pas de remplaçant.

mais nous avons écrit notre propre convertisseur ExpandoObject, vous pouvez prendre l'indice de cet article ASP.NET Core 3.0 : Custom JsonConverter pour le nouveau System.Text.Json

Nous n'avons besoin que de la sérialisation, nous créons donc simplement la sérialisation

J'ai été surpris que cela ne soit pas encore pris en charge.

Déplacer cela vers 5.0.

Passer à la 5.0 ? Cela signifie que nous devons attendre au moins un an? De retour à JSON.Net, c'est le cas.

5,0 ? wow, c'est vraiment nul.

Pour une solution de contournement laide temporelle, je peux utiliser JsonDocument avec un convertisseur personnalisé, mais c'est IDisposable.

public sealed class EventObject
    {
        [JsonPropertyName("id")]
        public long Id
        {
            get; set;
        }

        [JsonPropertyName("eventData")]
        [JsonConverter(typeof(JsonDocumentConverter))]
        public System.Text.Json.JsonDocument EventData
        {
            get; set;
        }
    }

    internal sealed class JsonDocumentConverter
        : JsonConverter<System.Text.Json.JsonDocument>
    {
        public override JsonDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonDocument.ParseValue(ref reader);
        }

        public override void Write(Utf8JsonWriter writer, JsonDocument value, JsonSerializerOptions options)
        {
            value.WriteTo(writer);
        }
    }

Nous ne sommes plus en mesure d'utiliser l'action POST suivante en présence de System.Text.Json :

[HttpPost]
public async Task<IActionResult> SubmitAsync(dynamic model)

Au lieu de cela, nous avons dû utiliser la méthode suivante, mais il n'y avait pas de méthode simple pour utiliser « modèle » dans le code backend en aval :

[HttpPost]
public async Task<IActionResult> SubmitAsync(JsonElement model)

« modèle » est un objet complexe et il peut contenir une collection d'objets et/ou d'autres objets complexes imbriqués. Pour pouvoir conclure un objet dynamic sur JsonElement , nous avons dû appeler model.GetRawText() et demander à Newtonsoft de le décoder en un objet dynamique. Cette méthode n'est pas celle souhaitée car l'objectif principal de cet exercice était de désactiver Newtonsoft.json du projet.

Puis-je supposer que la résolution de ce ticket/problème implique une solution au problème que nous avons rencontré ? Cela semble être un problème un peu urgent à résoudre, alors peut-il être traité le plus tôt possible ?

/cc @ahsonkhan @terrajobst

.NET Core 3.0 JsonSerializer.Deserialize en objet dynamique

Prise en charge de JsonSerializer pour ExpandoObject (mesures provisoires)
Je suis débutant, beaucoup d'endroits ne sont pas parfaits, bienvenue à tout le monde pour modifier
.net Core3 pas de support

Ajouter le convertisseur Json

ajouter en utilisant :

  • en utilisant System.Text.Json ;
  • en utilisant System.Text.Json.Serialization ;

```C#
///


/// Convertisseur Dynamique Temp
/// par:[email protected]
///

classe publique DynamicJsonConverter : JsonConverter
{
public override lecture dynamique (ref lecteur Utf8JsonReader,
Tapez typeToConvert,
JsonSerializerOptions options)
{

        if (reader.TokenType == JsonTokenType.True)
        {
            return true;
        }

        if (reader.TokenType == JsonTokenType.False)
        {
            return false;
        }

        if (reader.TokenType == JsonTokenType.Number)
        {
            if (reader.TryGetInt64(out long l))
            {
                return l;
            }

            return reader.GetDouble();
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            if (reader.TryGetDateTime(out DateTime datetime))
            {
                return datetime;
            }

            return reader.GetString();
        }

        if (reader.TokenType == JsonTokenType.StartObject)
        {
            using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
            return ReadObject(documentV.RootElement);
        }
        // Use JsonElement as fallback.
        // Newtonsoft uses JArray or JObject.
        JsonDocument document = JsonDocument.ParseValue(ref reader);
        return document.RootElement.Clone();
    }

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
    {
        object? result = null;
        switch (jsonElement.ValueKind)
        {
            case JsonValueKind.Object:
                result = ReadObject(jsonElement);
                break;
            case JsonValueKind.Array:
                result = ReadList(jsonElement);
                break;
            case JsonValueKind.String:
                //TODO: Missing Datetime&Bytes Convert
                result = jsonElement.GetString();
                break;
            case JsonValueKind.Number:
                //TODO: more num type
                result = 0;
                if (jsonElement.TryGetInt64(out long l))
                {
                    result = l;
                }
                break;
            case JsonValueKind.True:
                result = true;
                break;
            case JsonValueKind.False:
                result = false;
                break;
            case JsonValueKind.Undefined:
            case JsonValueKind.Null:
                result = null;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        return result;
    }

    private object? ReadList(JsonElement jsonElement)
    {
        IList<object?> list = new List<object?>();
        foreach (var item in jsonElement.EnumerateArray())
        {
            list.Add(ReadValue(item));
        }
        return list.Count == 0 ? null : list;
    }
    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
    {
       // writer.WriteStringValue(value.ToString());
    }
}
## How to Use?

```C#
var serializerOptions = new JsonSerializerOptions
{
    Converters = { new DynamicJsonConverter() }
};
return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

@tchivs , votre solution a fonctionné pour moi ; mais comme la propriété Converters est en lecture seule, j'ai dû faire quelque chose comme ceci :
c# var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new DynamicJsonConverter()); return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

Essayez d'utiliser le type JsonElement :

public JsonElement MyProperty {get; set;}

@tchivs , j'ai apporté quelques modifications à votre code - le retravaillant pour qu'il utilise des types de "projection" générés dynamiquement (ayant un sous-ensemble de propriétés d'un type de base), plutôt que ExpandoObject. J'ai posté le code (dans un exemple de projet de console) ici : EDennis.DynamicDeserialization .

Je testerai cette approche avec des objets plus compliqués et dans divers scénarios (par exemple, désérialisation d'objets dynamiques utilisés pour corriger des entités EF Core existantes et entièrement typées ; désérialisation d'objets json et de tableaux pour les cas de test). Faites-moi savoir si vous trouvez cela utile ou si vous voyez quelque chose qui pose problème.

Merci pour la solution de contournement de la communauté. Il est surprenant que Microsoft ne soit pas en mesure de proposer cette fonctionnalité dans un délai raisonnable. Travailler avec des réponses GraphQL sans objet dynamique quelconque conduit à beaucoup de code verbeux et laid. Ou même simplement vérifier l'existence de propriétés profondément imbriquées.

J'ai lu le fil des commentaires et la plupart se concentrent sur la désérialisation, je suis confronté à un problème où la sérialisation d'objets dynamiques apparemment "échoue" en silence. Pour tenter de reproduire mon scénario, j'ai proposé la reproduction minimale suivante :

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;

namespace SampleConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic d = new CustomDynamicObject();
            d.AProperty = 10;
            var s = JsonSerializer.Serialize(d);

            Console.WriteLine(s);
            Console.Read();
        }
    }

    class CustomDynamicObject : DynamicObject 
    {
        private readonly IDictionary<string, object> properties = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result) => properties.TryGetValue(binder.Name, out result);

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            properties[binder.Name] = value;
            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            foreach (var item in properties.Keys)
            {
                yield return item;
            }
        }
    }
}

Lorsqu'il est exécuté, s vaut {} , donc la sérialisation n'échoue pas mais génère un objet json vide.

Est-ce le bon problème ? ou dois-je relancer/suivre un autre ?

Est-ce le bon problème ? ou dois-je relancer/suivre un autre ?

C'est la bonne question. Pas besoin d'en ouvrir une pour les deux moitiés d'une même caractéristique. Ce problème concerne l'ajout de la prise en charge de l'objet expando (ce qui signifie pour les deux côtés, sérialiser et désérialiser).

J'ai rencontré ce problème aujourd'hui - je voulais dire que JsonElement fonctionnerait bien s'il ne dépendait pas de JsonDocument . Une façon de contourner ce problème est d'implémenter un destructeur pour JsonDocument afin que son élimination puisse être reportée à plus tard - une fois que tous les objets JsonElement sont collectés.

J'avais aussi besoin d'un objet dynamique. Cependant, il n'est pas encore mis en œuvre. Ma solution était d'utiliser un dictionnaire.
JsonSerializer.Deserialize<Dictionary<string, string>>(response)
Et puis recherchez la clé dont j'ai besoin :)

Juste pour ma propre santé mentale - le problème concerne-t-il spécifiquement la prise en charge d'ExpandoObject 5.0 ou la possibilité de désérialiser en un objet dynamique ? La conversation ici ne semble pas toutes correspondre au titre du problème, et j'ai définitivement besoin de ce dernier. Dans mon cas, je désérialise une dynamique imbriquée, en particulier List<Dictionary<string, dynamic>> . Retourné à Newtonsoft pour l'instant :(

Je voulais juste partager quelques mods de sucre de syntaxe c # 8 du code utile de

/// <summary>
/// Temp Dynamic Converter with c# 8
/// by:[email protected]
/// </summary>
public class DynamicJsonConverter : JsonConverter<dynamic>
{
    public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),
            JsonTokenType.String => reader.TryGetDateTime(out DateTime datetime) ? datetime.ToString() : reader.GetString(),
            JsonTokenType.StartObject =>  ReadObject(JsonDocument.ParseValue(ref reader).RootElement),
            // Use JsonElement as fallback.
                _ =>JsonDocument.ParseValue(ref reader).RootElement.Clone()
        };

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
        =>
         jsonElement.ValueKind switch
        {
            JsonValueKind.Object => ReadObject(jsonElement),
            JsonValueKind.Array => ReadList(jsonElement),
            JsonValueKind.String => jsonElement.GetString(),
            JsonValueKind.Number =>  jsonElement.TryGetInt64(out long l) ? 1 :0,
            JsonValueKind.True => true,
            JsonValueKind.False =>false,
            JsonValueKind.Undefined => null,
            JsonValueKind.Null => null,
                _ => throw new ArgumentOutOfRangeException()
        };

    private object? ReadList(JsonElement jsonElement)
    {
        var list = new List<object?>();
        jsonElement.EnumerateArray().ToList().ForEach(j => list.Add(ReadValue(j)));
        return list.Count == 0 ? null : list;
    }

    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
        {
        // writer.WriteStringValue(value.ToString());
        }
}

@ rs38 merci pour le code ici, c'était exactement ce dont j'avais besoin. Je voulais souligner un changement très subtil mais important nécessaire. Les deux lignes qui analysent le type "Number" sont incorrectes dans votre version compressée :

JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),

devrait être

JsonTokenType.Number => reader.TryGetInt64(out long l) ? l : reader.GetDouble(),

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? 1 :0,

devrait être

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? l :0,

@layomia Ce n'est pas grave. Le support aurait dû être fait il y a longtemps (en fait System.Text.Json n'aurait pas dû être lancé sans lui à mon avis) ! Et nous n'avons même pas de date limite !

Mon scénario concerne CosmosDB. J'interroge à l'aide du nouveau SDK .NET qui utilise ce JsonSerializer et comme il s'agit d'une base de données sans schéma, je ne veux pas créer de classe pour chaque projection des données de la base de données que je dois faire (il y a un tas de requêtes différentes) .
J'ai besoin des résultats des requêtes sous forme de listes d'objets dynamiques.

@SocVi100 Ne mettez pas vos espoirs sur Microsoft pour celui-ci. Mieux vaut s'en tenir à Json.net de Newtonsoft. @layomia a

pourquoi ne contribuez-vous pas ? mettre en œuvre et mettre dans un PR !

https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/adding-api-guidelines.md
https://github.com/dotnet/runtime/blob/master/docs/area-owners.md (Responsable :
@ericstj |, propriétaires : @layomia @steveharter @jozkee)

pourquoi ne contribuez-vous pas ? mettre en œuvre et mettre dans un PR !

SI seulement je pouvais.

Mon scénario concerne CosmosDB. J'interroge à l'aide du nouveau SDK .NET qui utilise ce JsonSerializer et comme il s'agit d'une base de données sans schéma, je ne veux pas créer de classe pour chaque projection des données de la base de données que je dois faire (il y a un tas de requêtes différentes) .
J'ai besoin des résultats des requêtes sous forme de listes d'objets dynamiques.

Il est possible de configurer un sérialiseur différent, en utilisant CosmosClientBuilder.WithCustomSerializer .

Voici un exemple : CosmosJsonNetSerializer

Nous ne pouvions pas intégrer cette fonctionnalité dans 5.0. Nous devions établir des priorités avec le reste du travail sur les fonctionnalités 5.0, et celui-ci ne correspondait pas. FWIW, c'était très proche de la ligne de coupe, d'où le mouvement tardif.

@layomia a

@RobbyDeLaet pouvons-nous essayer de garder cette discussion constructive ? Nous essayons de faire de notre mieux pour répondre aux principales fonctionnalités demandées par les clients tout en conservant les principes de conception. En ce qui concerne la parité des fonctionnalités avec Newtonsoft.Json, voici ce que nous avons à dire .

System.Text.Json se concentre principalement sur les performances, la sécurité et la conformité aux normes. Pour certains scénarios, System.Text.Json n'a pas de fonctionnalité intégrée, mais il existe des solutions de contournement recommandées. Si votre application dépend d'une fonctionnalité manquante, envisagez de déposer un problème pour savoir si la prise en charge de votre scénario peut être ajoutée.

Nous ne visons pas à remplacer Newtonsoft.JSON. Si cela fonctionne pour vous, continuez à l'utiliser. Nous ferons de notre mieux pour rendre System.Text.Json aussi utile que possible tout en maintenant les gains que nous avons obtenus en termes de performances, de sécurité, de conformité aux normes et de superposition. Au fil du temps, nous espérons le faire fonctionner pour autant de personnes que possible. J'entends l'intérêt ici et je veillerai à ce que nous nous concentrions sur cette évolution.

Merci pour vos suggestions. Je suis finalement passé au milieu, en utilisant le sérialiseur Newtonsoft uniquement pour la désérialisation, mais je dois encore tester. J'espère que cela ne durera pas trop longtemps pour obtenir le support ExpandoObject sur le sérialiseur par défaut de CosmosDB afin que je puisse me débarrasser de l'ancien.
@RobbyDeLaet , merci pour votre commentaire ! Je n'ai pas beaucoup d'attentes quant à la mise en œuvre par Microsoft de quoi que ce soit, je me suis senti trop souvent frustré à leur sujet. En tant que développeur indépendant, j'ai attendu pendant des années, littéralement, que des fonctionnalités essentielles soient implémentées sur l'infrastructure CosmosDB, EntityFramework et Identity, et elles font leur propre chemin en nous ignorant. Peu importe le nombre de votes qu'ils ont sur UserVoice ou autre. Je suppose que de telles fonctionnalités ne sont pas nécessaires pour ses propres développements et sont donc hors de portée...
Le problème est toujours le même : trop de marketing = trop d'attentes

Pour aider ceux qui ont besoin de cette fonctionnalité, consultez les exemples de code ci-dessus pour aider à débloquer :

  • De @tchivs (qui utilise ExpandoObject pour les objets et List<object> pour les collections)
  • De @denmitchell (qui utilise IL Emit pour les objets au lieu de ExpandoObject )

Pour répondre aux exigences de cette fonctionnalité, je fournirai un nouvel exemple de convertisseur personnalisé et le lierai ici lorsqu'il sera prêt. Après cela, j'ajouterai cet échantillon à la @tdykstra

Un détail qui n'a pas encore été abordé est que la prise en charge de dynamic nécessite une référence au très grand assemblage System.Linq.Expressions.dll ; cette complication peut signifier à l'avenir que nous ajoutons le convertisseur dynamique dans un nouvel assemblage (par exemple System.Text.Json.Converters.dll ) pour obtenir un paiement pour jouer pour ceux qui se soucient de la taille du déploiement (comme une application autonome ou un Blazor application cliente).

Juste pour ma propre santé mentale - le problème concerne-t-il spécifiquement la prise en charge d'ExpandoObject 5.0 ou la possibilité de désérialiser en un objet dynamique ? La conversation ici ne semble pas toutes correspondre au titre du problème, et j'ai définitivement besoin de ce dernier. Dans mon cas, je désérialise une dynamique imbriquée, en particulier List<Dictionary<string, dynamic>> . Retourné à Newtonsoft pour l'instant :(

Je teste maintenant une ancienne fonction que j'ai implémentée pour désérialiser les objets imbriqués et je vois le même problème noté dans cette rubrique avec "valueKind".

Y avait-il un moyen de résoudre ce problème en utilisant les nouveaux packages neetonsoft.json ? Parce que le code ci-dessus a fonctionné pour l'objet de niveau racine mais pas pour les objets imbriqués.

@ericstj Désolé , ma réaction a peut-être été un peu trop négative et dure, mais comme @SocVi100, je suis un développeur indépendant et parfois les frustrations

Juste pour ma propre santé mentale - le problème concerne-t-il la 5.0 spécifiquement la prise en charge d'ExpandoObject ou la possibilité de désérialiser en un objet dynamique

Je suppose que la sémantique souhaitée doit avoir un type dynamique, donc après la désérialisation, vous pouvez accéder à toutes les propriétés (y compris imbriquées) de manière tardive :

dynamic obj = JsonSerializer.Deserialize<dynamic>(json);
string s = obj.MyChildProperty.MyStringList[2];

et je suppose que la sémantique souhaitée prend également en charge ExpandoObject explicitement :

ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
dynamic obj = expando;
string s = obj.MyChildProperty.MyStringList[2];

Donc ma compréhension de la portée:

  • Deserialize<dynamic>(json) renvoie soit une primitive, une collection ou un objet. Un objet ici sera probablement ExpandoObject mais pourrait être IDynamicMetaObjectProvider ou un type JITed selon l'implémentation. C'est différent d'aujourd'hui qui renvoie toujours JsonElement .
  • Deserialize<ExpandoObject>(json) (qui implémente IDynamicMetaObjectProvider ) devrait créer un ExpandoObject approprié qui à son tour peut être utilisé avec dynamic . C'est différent d'aujourd'hui qui ne crée que des propriétés expando pour les propriétés racine et des instances JsonElement pour toutes les propriétés imbriquées.
  • Serialize<ExpandoObect>() fonctionne comme prévu
  • Serialize<IDynamicMetaObjectProvider ()> peut être implémenté ou non selon qu'il existe des scénarios où ExpandoObject n'est pas utilisé.

Sémantique en 3.0 - 5.0 :

  • La sérialisation d'un ExpandoObject fonctionne puisque ExpandoObject implémente IDictionary<string, object> et STJ sérialisera correctement chaque object , qu'il s'agisse d'une primitive, d'une collection ou d'un objet.
  • La désérialisation dans une sorte de ExpandoObject fonctionne, mais seules les propriétés racine sont des propriétés expando "correctes" ; toutes les propriétés imbriquées seront JsonElement il y a donc une incohérence dans le modèle de programmation, donc la désérialisation de ExpandoObject doit être évitée à moins qu'un convertisseur personnalisé ne soit utilisé pour cela.

Options pour 3.0-5.0 :

  • Écrivez un convertisseur personnalisé pour ExpandoObject et/ou object comme mentionné dans les articles ci-dessus ; Je fournirai bientôt un lien vers un nouvel échantillon qui correspond idéalement à une fonctionnalité 6.0.
  • Si vous ne faites que sérialiser, pas désérialiser, vous pouvez utiliser ExpandoObject (ou dynamic si le type est basé sur ExpandoObject ). Étant donné que la désérialisation de ExpandoObject a un problème d'incohérence aujourd'hui dans STJ, ExpandoObject n'est pas recommandé en cas de désérialisation.
  • Utilisez JsonElement au lieu d'objets dynamiques. JsonElement n'essaie pas de "deviner" à quel type le JSON est mappé, vous devez donc être explicite en appelant GetString(), GetInt32(), etc.

De plus, selon la mise en œuvre, la mise en œuvre d'un convertisseur personnalisé signifie probablement qu'il y a des « devinettes » sur les types de CLR auxquels le JSON est mappé pendant la désérialisation. Par exemple, une chaîne JSON peut correspondre à un DateTime ou à un string , un numéro JSON peut être mappé à un double ou à un long , et le Le type de tableau JSON doit être déterminé. Cela doit être vérifié et comparé à la sémantique de Newtonsoft. Cela devrait également s'aligner sur le type renvoyé lorsqu'une propriété est de type object (aujourd'hui, c'est JsonElement ).

Voici quelques exemples de sémantique 3.0 - 5.0 STJ :

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;

namespace ConsoleApp
{
    class Program
    {
        const string ExpectedJson = "{\"A\":\"A\",\"B\":[1,2],\"C\":42,\"D\":\"2020-01-01T00:00:00\",\"E\":{\"A_Child\":\"A_Child\"}}";

        static void Main(string[] args)
        {
            DateTime dateTime = new DateTime(2020, 1, 1);

            dynamic myDynamicChild = new ExpandoObject();
            myDynamicChild.A_Child = "A_Child";

            dynamic myDynamic = new ExpandoObject();
            myDynamic.A = "A";
            myDynamic.B = new List<int>() { 1, 2 };
            myDynamic.C = 42;
            myDynamic.D = dateTime;
            myDynamic.E = myDynamicChild;

            // Verify we can call late-bound property.
            int c = myDynamic.C;
            Debug.Assert(c == 42);

            // STJ can serialize with ExpandoObject since it implements IDictionary<string, object>.
            string json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Using 'dynamic' against backing ExpandoObject works.
            json = JsonSerializer.Serialize<dynamic>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Deserialize with <dynamic>, <object> and <JsonElement>.
            // For 5.0, using one of these is recommended over ExpandoObject because the programming model will be
            // consistent for the root type and all nested types.
            // Using <JsonElement> makes it clear and non-abiguous.
            // Using <object> by default uses 'JsonElement', but can be overridden by a custom converter.
            // Using <dynamic> uses 'object' which uses 'JsonElement'.
            {
                dynamic d = JsonSerializer.Deserialize<dynamic>(json);
                VerifyJsonElement(d);

                try
                {
                    // We will get an exception here if we try to access a dynamic property since 'object' is deserialized
                    // as a JsonElement and not an ExpandoObject.
                    c = d.C;
                    Debug.Fail("Should have thrown Exception!");
                }
                catch (Exception ex)
                {
                    Debug.Assert(ex.Message == "'System.Text.Json.JsonElement' does not contain a definition for 'C'");
                }

                // Serializing with <object> creates a JsonElement by default (can be changed by a custom converter).
                object o = JsonSerializer.Deserialize<object>(json);
                VerifyJsonElement((JsonElement)o);

                // Serialize with explicit <JsonElement>.
                JsonElement e = JsonSerializer.Deserialize<JsonElement>(json);
                VerifyJsonElement(e);
            }

            // Deserialize with ExpandoObject. This creates an ExpandoObject with the root Type having Expando-properties
            // but the value of those properties will be JsonElement. All other nested properties\objects\collections will
            // also be JsonElement. Due to the inconsistency of having only root-level Expando-properties (such as A_Child),
            // deserializing as ExpandoObject is not recommended (unless a ExpandoObject custom converter is used).
            {
                // When STJ deserializes, it creates ExpandoObjects via IDictionary<string, object> where 'object' is JsonElement.
                ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
                Debug.Assert(((IDictionary<string, object>)expando).Keys.Count == 5);
                myDynamic = expando;

                JsonElement jsonElement = myDynamic.A;
                Debug.Assert(jsonElement.GetString() == "A");

                jsonElement = myDynamic.B;
                Debug.Assert(jsonElement.EnumerateArray().Count() == 2);

                jsonElement = myDynamic.C;
                Debug.Assert(jsonElement.GetInt32() == 42);

                jsonElement = myDynamic.D;
                Debug.Assert(jsonElement.GetDateTime() == dateTime);

                jsonElement = myDynamic.E;
                // Here we have an inconsistency. Nested object property must use JsonElement (not a dynamic property).
                Debug.Assert(jsonElement.GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize works as expected.
                json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
                Debug.Assert(json == ExpectedJson);

                // Re-serialize works as expected; dynamic works here since backed by ExpandoObject in this example.
                json = JsonSerializer.Serialize<dynamic>(myDynamic);
                Debug.Assert(json == ExpectedJson);
            }

            void VerifyJsonElement(JsonElement elem)
            {
                // Verify JsonElement
                Debug.Assert(elem.GetProperty("A").GetString() == "A");
                Debug.Assert(elem.GetProperty("B").EnumerateArray().Count() == 2);
                Debug.Assert(elem.GetProperty("C").GetInt32() == 42);
                Debug.Assert(elem.GetProperty("D").GetDateTime() == dateTime);
                Debug.Assert(elem.GetProperty("E").GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize
                json = JsonSerializer.Serialize<dynamic>(elem);
                Debug.Assert(json == ExpectedJson);

                json = JsonSerializer.Serialize<JsonElement>(elem);
                Debug.Assert(json == ExpectedJson);
            }
        }
    }
}

@rs38 Pouvez-vous corriger votre extrait de code en fonction de ce que @ryan-hollister-q2 a souligné ?

Comme promis, le PR fournissant un exemple d'implémentation dynamique se trouve sur https://github.com/dotnet/runtime/pull/42097.

@rs38 Pouvez-vous corriger votre extrait de code en fonction de ce que @ryan-hollister-q2 a souligné ?

ce n'était pas mon code, j'ai juste partagé quelques extraits de @tchivs plus tôt dans ce fil.

Cette page vous a été utile?
0 / 5 - 0 notes