Runtime: Le nouveau Asp.NET Core 3.0 Json ne sérialise pas le dictionnaire<key/>

Créé le 7 août 2019  ·  51Commentaires  ·  Source: dotnet/runtime

.NET Core 3.0 Aperçu 7

Asp.NET Web Apis, lors du renvoi d'un dictionnaire, il échoue avec une exception NotSupported. J'ai inclus l'exception ci-dessous.

De plus, la méthode ControllerBase.BadRequest prend un ModelStateDictionary , mais lorsque cela est renvoyé, le sérialiseur explose également avec une NotSupportedException, mais un message légèrement différent.

Quand ce support sera-t-il ajouté ? Depuis que cela est pris en charge dans Json.net et d'autres sérialiseurs depuis un certain temps, j'espère que cela est sur le radar.

J'apprécie le fait de pouvoir réutiliser Json.net, alors merci beaucoup pour cela !

Exception lors du renvoi d'un dictionnaire
System.NotSupportedException : le type de collection « System.Collections.Generic.Dictionary`2[System.Int32,System.String] » n'est pas pris en charge.
à System.Text.Json.JsonClassInfo.GetElementType (Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
à System.Text.Json.JsonClassInfo.CreateProperty (Type declarePropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
à System.Text.Json.JsonClassInfo.AddProperty (Type propertyType, PropertyInfo propertyInfo, Type classType, options JsonSerializerOptions)
à System.Text.Json.JsonClassInfo.AddPolicyProperty (Type propertyType, options JsonSerializerOptions)
à System.Text.Json.JsonClassInfo..ctor (Type de type, options JsonSerializerOptions)
à System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType)
à System.Text.Json.WriteStackFrame.Initialize (Type de type, options JsonSerializerOptions)
à System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Valeur de l'objet, Type de type, Options JsonSerializerOptions, CancellationToken cancelToken)
à Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
à Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0 (invocateur ResourceInvoker, résultat IActionResult)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0 TFilter,TFilterAsync
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexte ResultExecutedContextSealed)
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter,TFilterAsync
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- Fin de la trace de la pile à partir de l'emplacement précédent où l'exception a été levée ---
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(Invocateur ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1 (invocateur ResourceInvoker)
sur Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, logger ILogger)
à Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexte HttpContext)
à Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexte HttpContext)
à Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexte HttpContext)
à Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
à Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
à Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexte HttpContext)

Exception lors du retour de BadRequest
System.NotSupportedException : le type de collection « Microsoft.AspNetCore.Mvc.SerializableError » n'est pas pris en charge.
à System.Text.Json.JsonClassInfo.GetElementType (Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
à System.Text.Json.JsonClassInfo.CreateProperty (Type declarePropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
à System.Text.Json.JsonClassInfo.AddProperty (Type propertyType, PropertyInfo propertyInfo, Type classType, options JsonSerializerOptions)
à System.Text.Json.JsonClassInfo.AddPolicyProperty (Type propertyType, options JsonSerializerOptions)
à System.Text.Json.JsonClassInfo..ctor (Type de type, options JsonSerializerOptions)
à System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType)
à System.Text.Json.WriteStackFrame.Initialize (Type de type, options JsonSerializerOptions)
à System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Valeur de l'objet, Type de type, Options JsonSerializerOptions, CancellationToken cancelToken)
à Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
à Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0 (invocateur ResourceInvoker, résultat IActionResult)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0 TFilter,TFilterAsync
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexte ResultExecutedContextSealed)
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter,TFilterAsync
à Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- Fin de la trace de la pile à partir de l'emplacement précédent où l'exception a été levée ---
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(Invocateur ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
sur Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1 (invocateur ResourceInvoker)
sur Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, logger ILogger)
à Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexte HttpContext)
à Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexte HttpContext)
à Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexte HttpContext)
à Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
à Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
à Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexte HttpContext)

area-System.Text.Json enhancement

Commentaire le plus utile

Pour les nouveaux arrivants, la solution temporaire est de revenir à Newtonsoft.Json .

  1. Ajoutez une référence de package à Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Ajoutez .AddNewtonsoftJson() juste après .AddControllers() / .AddMvc() ou toute autre combinaison.

Tous les 51 commentaires

Les deux erreurs « exception non prise en charge » sont des limitations au sein du sérialiseur intégré et sont inhérentes à la conception (au moins pour ce qui est expédié dans la version 3.0).

Quand ce support sera-t-il ajouté ? Depuis que cela est pris en charge dans Json.net et d'autres sérialiseurs depuis un certain temps, j'espère que cela est sur le radar.
J'apprécie le fait de pouvoir réutiliser Json.net, alors merci beaucoup pour cela !

Il existe un tas de capacités de sérialisation qui sont sur notre radar pour être prises en charge dans vNext (donc 5.0) et au-delà, la prise en charge du dictionnaire personnalisé étant l'une d'entre elles.

Asp.NET Web Apis, lors du renvoi d'un dictionnaire, il échoue avec une exception NotSupported

Lors de la sérialisation, seul Dictionary<string, TValue> est pris en charge aujourd'hui (c'est-à-dire les TKeys qui sont des chaînes). Votre dictionnaire est de <int, string> qui n'est pas pris en charge.
https://github.com/dotnet/corefx/blob/93d7aa1c1737b6da29d04b78557215e18eb786d4/src/System.Text.Json/tests/Serialization/DictionaryTests.cs#L385 -L390

@steveharter , @layomia - existe-t-il une solution de contournement potentielle ici en attendant ? Que faudrait-il pour ajouter la prise en charge du dictionnaire sans clé de chaîne dans le sérialiseur pour 5.0 ?

System.NotSupportedException : le type de collection « Microsoft.AspNetCore.Mvc.SerializableError » n'est pas pris en charge.

~ L'ajout de la prise en charge d'un type comme SerializableError n'était pas sur mon radar. @pranavkm , @rynowak - quel est le contexte ici ? Je ne connais pas ModelStateDictionary , cela pourrait-il être pris en charge dans mvc lui-même avec un convertisseur personnalisé ?~

Edit : peu importe, c'est déjà corrigé.

System.NotSupportedException : le type de collection « Microsoft.AspNetCore.Mvc.SerializableError » n'est pas pris en charge.

Il s'agissait d'un problème connu https://github.com/aspnet/AspNetCore/issues/11459 qui a été récemment corrigé (dans le cadre de l'aperçu 8) : https://github.com/dotnet/corefx/pull/39001

Merci beaucoup pour vos réponses rapides @ahsonkhan !

La "limitation" de la clé étant une chaîne a du sens quand j'y pense. Je vois maintenant que Json.net génère en fait json avec la clé étant une chaîne, lors de la désérialisation, cela me ferait simplement récupérer un int. Ce serait certainement bien d'avoir la prise en charge des clés sans chaîne à l'avenir, mais pas un obstacle.

Ok, heureux d'apprendre que l'erreur Mvc.SerializableError non prise en charge a été corrigée. Avez-vous des idées pour savoir s'il y a une date de sortie espérée pour Preview 8 ? J'ai essayé de chercher et de trouver quelque chose, mais je n'ai rien vu à ce sujet.

Une fois que l'aperçu 8 sera disponible, nous essaierons à nouveau d'essayer la bibliothèque de sérialisation json .net core 3, mais pour l'instant, nous devons nous en tenir à Json.net.

@steveharter , @layomia - existe-t-il une solution de contournement potentielle ici en attendant ? Que faudrait-il pour ajouter la prise en charge du dictionnaire sans clé de chaîne dans le sérialiseur pour 5.0 ?

>

@ahsonkhan @willyt150 la solution de contournement consiste à utiliser un convertisseur personnalisé qui implémente JsonConverter<T>T est Dictionary<int, string> .
Voir https://github.com/dotnet/corefx/issues/36639#issue -429928740 pour quelques exemples.

Avez-vous des idées pour savoir s'il y a une date de sortie espérée pour Preview 8 ?

Quelque temps plus tard ce mois-ci.

En y réfléchissant un peu plus, en supprimant les mises à disposition pour le moment, car il se peut que ce soit quelque chose que nous ne voulons pas prendre en charge par défaut.

Merci @layomia je vais regarder ça.

Merci @ahsonkhan , dans l'attente du correctif !

De @namse (de https://github.com/dotnet/corefx/issues/40404) :

Bonjour. Lorsque j'essaie de sérialiser le dictionnaire avec une clé entière, il lève System.NotSupportedException.

Je pense qu'il est logique de prendre en charge la sérialisation Json dont le dictionnaire a ToString clé ToString -able.

Version

System.Text.Json Nuget Version : 4.6.0-preview8.19405.3

Code

var dictionary = new Dictionary<int, string>
{
  [5] = "five"
};
JsonSerializer.Serialize(dictionary);

Attendu

"{"5": "five"}"

Mais que se passe-t-il

Erreur System.NotSupportedException Lancé

En fait, il y a un problème de compatibilité lorsque je change Newtonsoft.Json en System.Text.Json. Ils renvoient la chaîne comme je m'y attendais. Je pense que System.Text.Json n'a pas besoin d'être compatible mais... vous savez.

J'ai implémenté un convertisseur qui prend en charge à la fois la sérialisation et la désérialisation pour IDictionary<TKey, TValue>TKey a une méthode statique TKey Parse(string) :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace JsonDictionaryConverter
{
    sealed class JsonNonStringKeyDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
    {
        public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var convertedType = typeof(Dictionary<,>)
                .MakeGenericType(typeof(string), typeToConvert.GenericTypeArguments[1]);
            var value = JsonSerializer.Deserialize(ref reader, convertedType, options);
            var instance = (Dictionary<TKey, TValue>)Activator.CreateInstance(
                typeToConvert,
                BindingFlags.Instance | BindingFlags.Public,
                null,
                null,
                CultureInfo.CurrentCulture);
            var enumerator = (IEnumerator)convertedType.GetMethod("GetEnumerator")!.Invoke(value, null);
            var parse = typeof(TKey).GetMethod("Parse", 0, BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Any, new[] { typeof(string) }, null);
            if (parse == null) throw new NotSupportedException($"{typeof(TKey)} as TKey in IDictionary<TKey, TValue> is not supported.");
            while (enumerator.MoveNext())
            {
                var element = (KeyValuePair<string?, TValue>)enumerator.Current;
                instance.Add((TKey)parse.Invoke(null, new[] { element.Key }), element.Value);
            }
            return instance;
        }

        public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializerOptions options)
        {
            var convertedDictionary = new Dictionary<string?, TValue>(value.Count);
            foreach (var (k, v) in value) convertedDictionary[k?.ToString()] = v;
            JsonSerializer.Serialize(writer, convertedDictionary, options);
            convertedDictionary.Clear();
        }
    }

    sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            if (!typeToConvert.IsGenericType) return false;
            if (typeToConvert.GenericTypeArguments[0] == typeof(string)) return false;
            return typeToConvert.GetInterface("IDictionary") != null;
        }

        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var converterType = typeof(JsonNonStringKeyDictionaryConverter<,>)
                .MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]);
            var converter = (JsonConverter)Activator.CreateInstance(
                converterType,
                BindingFlags.Instance | BindingFlags.Public,
                null,
                null,
                CultureInfo.CurrentCulture);
            return converter;
        }
    }
}

Test:

class Entity
{
    public string Value { get; set; }
}
class TestClass
{
    public Dictionary<int, Entity> IntKey { get; set; }
    public Dictionary<float, Entity> FloatKey { get; set; }
    public Dictionary<double, Entity> DoubleKey { get; set; }
    public Dictionary<DateTime, Entity> DateTimeKey { get; set; }
    public Dictionary<string, Entity> StringKey { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        var options = new JsonSerializerOptions();
        options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
        var x = new TestClass
        {
            IntKey = new Dictionary<int, Entity> { [1] = new Entity { Value = "test" } },
            FloatKey = new Dictionary<float, Entity> { [1.3f] = new Entity { Value = "test" } },
            DoubleKey = new Dictionary<double, Entity> { [1.35] = new Entity { Value = "test" } },
            DateTimeKey = new Dictionary<DateTime, Entity> { [DateTime.Now] = new Entity { Value = "test" } },
            StringKey = new Dictionary<string, Entity> { ["test"] = new Entity { Value = "test" } }
        };

        var value = JsonSerializer.Serialize(x, options);
        Console.WriteLine(value);
        var obj = JsonSerializer.Deserialize<TestClass>(value, options);
        Console.WriteLine(JsonSerializer.Serialize(obj, options));
    }
}

Résultat:

{"IntKey":{"1":{"Value":"test"}},"FloatKey":{"1.3":{"Value":"test"}},"DoubleKey":{"1.35":{"Value":"test"}},"DateTimeKey":{"8/25/2019 6:47:48 PM":{"Value":"test"}},"StringKey":{"test":{"Value":"test"}}}
{"IntKey":{"1":{"Value":"test"}},"FloatKey":{"1.3":{"Value":"test"}},"DoubleKey":{"1.35":{"Value":"test"}},"DateTimeKey":{"8/25/2019 6:47:48 PM":{"Value":"test"}},"StringKey":{"test":{"Value":"test"}}}

Cependant, il ne peut toujours pas sérialiser un dictionnaire imbriqué tel que Dictionary<int, Dictionary<int, int>> car System.Text.Json n'accepte pas le type interne Dictionary<int, int> . Je pense que c'est un bug.

Cependant, il ne peut toujours pas sérialiser un dictionnaire imbriqué tel qu'un dictionnaire> parce que System.Text.Json n'accepte pas le type interne Dictionary. Je pense que c'est un bug.

La prise en charge de seulement <string, x> est de par sa conception en raison de la réduction de la portée afin d'être livrée pour la v3.0. La version 3.0 est destinée à être un produit viable minimal avec les scénarios les plus courants pris en charge.

@steveharter Au moins, vous ne devriez pas lancer une exception notsupport lorsqu'il existe un convertisseur utilisable.

Existe-t-il des plans pour prendre en charge cela dans .net core 3.1 ?

Pour les nouveaux arrivants, la solution temporaire est de revenir à Newtonsoft.Json .

  1. Ajoutez une référence de package à Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Ajoutez .AddNewtonsoftJson() juste après .AddControllers() / .AddMvc() ou toute autre combinaison.

@steveharter Au moins, vous ne devriez pas lancer une exception notsupport lorsqu'il existe un convertisseur utilisable.

Oui c'est un bon point. Peut-être pouvons-nous supprimer cette restriction pour 3.1. cc @layomia

Aussi juste pour préciser qu'aujourd'hui les éléments du dictionnaire sont sérialisés comme des propriétés, ce qui est possible car la clé est une chaîne. La prise en charge des clés sans chaîne signifie que les éléments seront sérialisés en tant que KeyValuePair.

Oh mon Dieu, j'ai eu ce problème juste après la mise à niveau vers 3.0.

J'ai dû installer le package newton avec AddNewtonsoftJson.

De @israellot dans https://github.com/dotnet/corefx/issues/41345

var dictionary = new Dictionary<int, int>()
            {
                [0] = 1
            };

 var serialized = System.Text.Json.JsonSerializer.Serialize(dictionary);

Cette sérialisation simple est bien gérée par l'ancienne bibliothèque json par défaut de Newtonsoft en sérialisant la clé int en tant que chaîne. Sur System.Text.Json, une exception non prise en charge est levée.

@israellot , @unruledboy et d'autres sur le fil, pouvez-vous expliquer pourquoi votre modèle objet nécessite des dictionnaires avec des clés entières dans vos scénarios et pourquoi le changer en Dictionary<string, TValue> ne fonctionnerait pas ? J'aimerais voir quelques utilisations pour rassembler les exigences et aider à motiver le correctif.

Ils seraient de toute façon sérialisés en tant que chaînes, donc je ne comprends pas dans quels scénarios vous voudriez que votre dictionnaire sous-jacent ait des clés int32 à la place.

@ahsonkhan Je crois que la principale motivation est la compatibilité.
Le sérialiseur par défaut précédent était Newtonsoft, donc les utilisateurs peuvent avoir écrit des modèles entiers en s'appuyant sur sa capacité à sérialiser et désérialiser des classes arbitraires. La migration de 2.X vers 3.0 provoquera désormais un changement de rupture silencieux puisque nous ne connaîtrons l'incompatibilité qu'au moment de l'exécution.

Je pense que de nombreux scénarios impliquent l'utilisation de json comme transport à travers le fil, et dans ce cas, le modèle de domaine pourrait être Dictionary. Votre suggestion se résume à la création d'un dictionnaire d'objets DTO distinctet la conversion entre les deux, semble plutôt inefficace puisque nous devons maintenant allouer un autre objet juste pour être compatible avec le sérialiseur.
En regardant strictement le sérialiseur, restreindre le dictionnaire aux clés de chaîne est logique car c'est la seule représentation json possible. Mais étant donné que le sérialiseur joue un rôle dans les applications, je pense qu'il est préférable d'éliminer autant de frictions que possible.

J'ai rencontré ce problème avec un petit programme que j'écris où des parties d'une étiquette de version sont fournies via un fichier json.
Les parties d'étiquette ont une clé qui spécifie l'index où la partie d'étiquette peut être insérée. Cela signifie que les touches sont des valeurs numériques, par exemple

{
  "parts" : {
    "1" : "alpha",
    "3" : "beta"
  }
}

En utilisant Newtonsoft, le json peut être désérialisé sans problème en un Dictionary<int, string> . Après la conversion en System.Text.Json, la sérialisation a échoué.

J'ai résolu ce problème en créant mon propre DictionaryConverter et DictionaryConverter .
J'ai également créé un convertisseur simple qui permet de désérialiser des entiers à partir d'une chaîne

Ceux-ci sont ensuite enregistrés via les options du sérialiseur : https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22

Ces modifications permettent aux clés d'un dictionnaire d'être désérialisées au lieu de les lire directement sous forme de chaîne. Cela ouvre davantage la prise en charge des clés comme types arbitraires qui pourraient avoir leurs propres convertisseurs enregistrés pour la sérialisation (par exemple, enums/type/types qui peuvent être sérialisés en tant que clés uniques, etc.)

Je n'ai pas formellement testé les choses, mais dans le cadre du développement actuel, cela semble avoir résolu le problème.

Définition d'un jalon pour 3.1 pour supprimer toutes les restrictions qui empêchent la création d'un convertisseur personnalisé pouvant gérer n'importe quel TKey pour Dictionary<TKey,TValue> .

Mise à jour : j'ai ajouté des exemples qui fonctionnent avec 3.0. Je n'ai remarqué aucun problème, comme avec les dictionnaires imbriqués, comme indiqué ci-dessus.

Deux formats JSON sont utilisés dans les exemples :

  • Objet JSON avec propriétés de chaîne : {"1":"val1","2":"val2"} même si TKey n'est pas une chaîne.

    • Ceux-ci reposent sur une méthode TryParse sur le type de clé pris en charge correspondant. Pour la v3.0, il n'est pas possible de fournir un support pour un TKey généralisé car les méthodes TryParse peuvent être différentes pour tout TKey et parce qu'il y a des paramètres de culture qui doivent être fournis (typiquement Invariant). Les exemples ci-dessous sont donc pour un seul type TKey .

    • Sample Dictionary<int, string> . Ceci est un exemple simple pour seulement TValue == string .

    • Sample Dictionary<Guid, TValue> . Cela peut gérer n'importe quel TValue .

    • Sample Dictionary<TKey, TValue> where TKey is Enum . Pour les énumérations ; cela peut gérer n'importe quel TValue .

  • Tableau JSON avec entrées KeyValuePair : [{"Key":1,"Value":"val1"},{"Key":2,"Value":"val2"}]

Si ces exemples sont satisfaisants, je changerai ce problème en 5.0 afin de déterminer si nous fournissons une prise en charge intégrée qui ne nécessite pas de convertisseurs personnalisés.

Définir le jalon sur 5.0 pour examen (et si l'un des exemples ci-dessus devait fonctionner par défaut).

La désérialisation semble également mapper les types d'objets génériques sur JsonDocument plutôt que sur leurs types normaux (primitifs ?).

Exemple:

string test = "[{\"id\":86,\"name\":\"test\"}]";
var SystemTextJson = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, object>>>(test);
var NewtonSoftJson = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(test);

SystemTextJson[0]["id"] s'affiche sous la forme : ValueKind = Number : "86"
NewtonSoftJson[0]["id"] s'affiche sous la forme : 86

@steveharter Un dictionnaire à clé enum avec ce convertisseur sérialise comme :
{ "Stuff": [ { "Key": 1, "Value": "String" }, { "Key": 3, "Value": "String" }, { "Key": 2, "Value": "String" } ] }

Alors que JSON.NET donne ce à quoi je suppose que la plupart des gens s'attendent :
{ "Stuff": { "Item1": "String", "Item2": "String", "Item3": "String" }

La désérialisation semble également mapper les types d'objets génériques sur JsonDocument plutôt que sur leurs types normaux (primitifs ?).

Oui c'est par conception. Voir https://github.com/dotnet/corefx/issues/38713

@steveharter Un dictionnaire à clé enum avec ce convertisseur sérialise comme :
{ "Stuff": [ { "Key": 1, "Value": "String" }, { "Key": 3, "Value": "String" }, { "Key": 2, "Value": " Chaîne de caractères" } ] }
Alors que JSON.NET donne ce à quoi je suppose que la plupart des gens s'attendent :
{ "Stuff": { "Item1": "String", "Item2": "String", "Item3": "String" }

Je suppose que vous utilisez le « Exemple de dictionnaire"? Si c'est le cas, oui, cela a utilisé KeyValuePair qui a les propriétés "Key" et "Value". Je n'ai pas fourni d'échantillon pour les dictionnaires enum basés sur TKey qui sérialisent en tant que noms de propriété, mais je vais travailler pour l'ajouter.

Ouais, celui-là. Et ah ok, je pensais que vous vouliez dire cela en tant que sérialiseur de dictionnaire générique.
Intéressé de voir votre nouvel échantillon lorsqu'il sera disponible, car celui que nous utilisons actuellement ne semble pas aussi rapide que je le souhaiterais.

@roguecode voici un exemple Enum pour Dictionary<TKey, TValue> où TKey est un Enum et utilise la syntaxe JSON "property" au lieu de KeyValuePair. J'ai également mis à jour la liste des échantillons ci-dessus pour inclure ce nouvel échantillon.

Bonjour, j'ai quelque chose de similaire mais différent et je me demande si vous pouvez m'indiquer où chercher.

Newtonsoft.Json : 12.0.2
Microsoft.AspNetCore.Mvc.NewtonsoftJson : 3.0.0

Démarrage en version 3.0 :

                .AddMvc(mvcOptions => mvcOptions.EnableEndpointRouting = false)
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                .AddNewtonsoftJson();

Si j'ai cet objet problemDetails :

// problemDetails has 2 extension items
{Microsoft.AspNetCore.Mvc.ValidationProblemDetails}
    Detail: "Please refer to the 'errors' property for additional details."
    Errors: Count = 1
    Extensions: Count = 2
    Instance: "/api/test/complex-binding-from-query"
    Status: 400
    Title: "One or more validation errors occurred."
    Type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"

En 2.2, l'exécution de Newtonsoft.Json.JsonConvert.SerializeObject(problemDetails) renvoie

{"errors":{"Int":["The value 'a' is not valid for Int."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"detail":"Please refer to the 'errors' property for additional details.","instance":"/api/test/complex-binding-from-query","traceId":"0HLQQ40AFBJNG","correlationId":"0HLQQ40AFBJNG"}

En 3.0 il renvoie :

{"Errors":{"param":["The value 'a' is not valid."]},"Type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","Title":"One or more validation errors occurred.","Status":400,"Detail":"Please refer to the 'errors' property for additional details.","Instance":"/bad-request","Extensions":{"traceId":"|d0d40f80-48b6c9184401b0e1.","correlationId":"0HLQR10NSMRGD:00000009"}}

La chaîne sérialisée de la version 3.0 inclut l'IDictionarynom de la propriété, Extensions , et nous pouvons correctement désérialiser cette chaîne dans 3.0. Vous pouvez voir que ce nom de propriété est omis de la version 2.x.

Le problème est que la sérialisation 3.0 qui se produit lorsque la réponse est renvoyée par un filtre utilisant un BadRequestObjectResult , à partir du code ci-dessous :

public sealed class ProblemDetailsResultFilterAttribute : Attribute, IAlwaysRunResultFilter
{
        public void OnResultExecuting(ResultExecutingContext context)
        {
             context.Result = new BadRequestObjectResult(problemDetails);
        }
}

..., le contenu de la réponse qui est renvoyé est de la même forme que la version 2.2 (le nom de la propriété Extensions est exclu), ce qui provoque la désérialisation Extensions propriété Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationProblemDetails>() )

D'une manière ou d'une autre, cette sérialisation n'utilise pas la même sérialisation que la bibliothèque Newtonsoft avec laquelle nous essayons de désérialiser.

Merci pour la considération!

J'ai quelque chose de similaire mais différent

@ ts46235 pourriez-vous s'il vous plaît ouvrir un nouveau problème pour cela car il est différent du problème actuel ?

@ ts46235 J'ai répondu à votre question dans l'autre numéro que vous avez ouvert ici - https://github.com/aspnet/AspNetCore/issues/16618. Marquer la conversation ici comme hors sujet.

mis à jour vers Core 3.1 et toujours pas corrigé

Je viens de passer à 3.1 et j'ai été touché par ça. Retour à JSON.NET je vais... (j'utilise des clés GUID)

Dot net core 3.1
dictionnairene fonctionne pas non plus, même l'objet dans la clé est en fait une chaîne

Moi aussi, je viens de frapper ceci, et quelle limitation silencieuse effrayante, car comme d'autres l'ont souligné, vous ne le verrez pas au moment de la compilation. Dans mon cas, je souhaite sérialiser Dictionary<int, List<string>> , ce qui ne me semble pas particulièrement exotique.

Ils devraient le réparer mais je vois cela maintes et maintes fois même avec l'ancien formateur, le formateur binaire au début de newtsoft, les dictionnaires dans les dictionnaires, les dictionnaires avec interfaces. Ils devraient le réparer, mais si vous ne voulez pas de problèmes, les gens ne devraient vraiment pas mettre des objets complexes comme les dictionnaires dans les contrats de sérialisation, vous demandez des problèmes - newtsoft a gâté les gens. Regardez toutes les propriétés publiques sur le nombre de dictionnaires, etc., vous comptez sur quelque chose de personnalisé dans le sérialiseur pour mapper cela.

Malheureusement, il n'y a pas de type simple pour cela en C# pour les noms de propriété, donc le dictionnaire est forcé. Alors je suis juste triste..

Voici une solution de contournement, mais en aucun cas une solution complète :

   [JsonConverter(typeof(DictionaryConverter))]
   public Dictionary<string, object> ExtraProperties { get; set; } = new Dictionary<string, object>();
public class DictionaryConverter : JsonConverter<Dictionary<string, object>>
{
    public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var dictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);

        foreach (string key in dictionary.Keys.ToList())
        {
            if (dictionary[key] is JsonElement je)
            {
                dictionary[key] = Unwrap(je);
            }
        }

        return dictionary;
    }

    public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
        => JsonSerializer.Serialize(writer, value, options);

    private static object Unwrap(JsonElement je)
    {
        return je.ValueKind switch
        {
            JsonValueKind.String => je.ToString(),
            JsonValueKind.Number => je.TryGetInt64(out var l) ? l : je.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Array => je.EnumerateArray().Select(Unwrap).ToList(),
            JsonValueKind.Object => je.EnumerateObject().ToDictionary(x => x.Name, x => Unwrap(x.Value)),
            _ => null
        };
    }

Peut-être que la prise en charge de (certains des) types de sérialiseurs courants pourrait être ajoutée OOTB, par exemple cette liste , et aussi si l'un de ces types .IsAssignableFrom(systemDotObjectInstance.GetType()) en charge Dictionary<<ins i="7">object</ins>, V> .

Voici la proposition d'ajout de la prise en charge des types TKey sans chaîne dans les dictionnaires.
https://github.com/dotnet/runtime/pull/32676

Veuillez nous faire part de vos pensées ou préoccupations.

Plus précisément, veuillez fournir des commentaires sur l'ensemble de types que @Jozkee prévoit de prendre en charge les clés de dictionnaire, en particulier si vous avez besoin que d'autres types soient pris en charge (essentiellement, tous les types numériques primitifs intégrés, les énumérations et quelques autres) :
https://github.com/dotnet/runtime/blob/a5d96c0e280b56412d4614848f5ee3b1e0d7f216/src/libraries/System.Text.Json/docs/KeyConverter_spec.md#keyconverter

dictionnairene fonctionne pas non plus, même l'objet dans la clé est en fait une chaîne

@andrew-vdb, la prise en charge de la sérialisation de clés d'objet arbitraires restera probablement non prise en charge. Cependant, si le type d'exécution des clés d'objet est l'un des types "nouvellement pris en charge", la sérialisation fonctionnera pour cela une fois la fonctionnalité terminée. La désérialisation, cependant, restera en boîte JsonElement (jusqu'à ce que ce problème connexe pour l'activation du comportement différent soit résolu) : https://github.com/dotnet/runtime/issues/29960

@Jozkee quels sont les types activés pour TValue ? Vraisemblablement tout ce que vous pouvez actuellement sérialiser en tant qu'objet autonome ?

Vraisemblablement tout ce que vous pouvez actuellement sérialiser en tant qu'objet autonome ?

Oui.

La désérialisation semble également mapper les types d'objets génériques sur JsonDocument plutôt que sur leurs types normaux (primitifs ?).

Exemple:

string test = "[{\"id\":86,\"name\":\"test\"}]";
var SystemTextJson = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, object>>>(test);
var NewtonSoftJson = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(test);

SystemTextJson[0]["id"] s'affiche sous la forme : ValueKind = Number : "86"
NewtonSoftJson[0]["id"] s'affiche sous la forme : 86

De tous les problèmes mentionnés, celui-ci me dérange le plus. Listerou T[] ou Dictionnairedoit se désérialiser correctement pour tout type pouvant être mappé directement à partir des quelques types que Json possède vers les types CLR.

La version 3.0 est destinée à être un produit viable minimal avec les scénarios les plus courants pris en charge

Je me demande comment Lister> n'est pas l'un des scénarios les plus courants :

[// N objects
{"a":4},
{"b","Bla"},
]

Comme cela se désérialise également dans System.Text.JsonElement, où je m'attendrais à un double (Number) et à une chaîne (String)

J'ai rencontré ce problème avec un petit programme que j'écris où des parties d'une étiquette de version sont fournies via un fichier json.
Les parties d'étiquette ont une clé qui spécifie l'index où la partie d'étiquette peut être insérée. Cela signifie que les touches sont des valeurs numériques, par exemple

{
  "parts" : {
    "1" : "alpha",
    "3" : "beta"
  }
}

En utilisant Newtonsoft, le json peut être désérialisé sans problème en un Dictionary<int, string> . Après la conversion en System.Text.Json, la sérialisation a échoué.

J'ai résolu ce problème en créant mon propre DictionaryConverter et DictionaryConverter .
J'ai également créé un convertisseur simple qui permet de désérialiser des entiers à partir d'une chaîne

Ceux-ci sont ensuite enregistrés via les options du sérialiseur : https://github.com/Kieranties/SimpleVersion/blob/feature/netcore3/src/SimpleVersion.Core/Serialization/Serializer.cs#L20

Ces modifications permettent aux clés d'un dictionnaire d'être désérialisées au lieu de les lire directement sous forme de chaîne. Cela ouvre davantage la prise en charge des clés comme types arbitraires qui pourraient avoir leurs propres convertisseurs enregistrés pour la sérialisation (par exemple, enums/type/types qui peuvent être sérialisés en tant que clés uniques, etc.)

Je n'ai pas formellement testé les choses, mais dans le cadre du développement actuel, cela semble avoir résolu le problème.

salut @Kieranties , le github lie 404 pour moi

salut @Kieranties , le github lie 404 pour moi

@AirEssY J'ai corrigé les liens dans mon commentaire d'origine, je pense qu'il est maintenant en master sur : https://github.com/Kieranties/SimpleVersion/tree/master/src/SimpleVersion.Core/Serialization

Si le dictionnaire est équivalent à une carte JavaScript, alors n'importe quel (type JS représenté en C#) devrait être acceptable,

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

A Map's keys can be any value (including functions, objects, or any primitive).

Un exemple de l'approche standard pour désérialiser une carte en JS est :

const countries = new Map(JSON.parse('[[1,"Bahamas (the)"],[2,"Bolivia (Plurinational State of)"]]'))

console.log(countries)

Ce qui produit :

Map(2) {
  1 => 'Bahamas (the)',
  2 => 'Bolivia (Plurinational State of)'
}

TL; DR : restreindre les clés aux chaînes ne fonctionne pas bien avec JS

@Jozkee , est-ce donc uniquement dans .NET 5 ou dans 3.* ?

@onionhammer .NET 5.0, vous pouvez également essayer la fonctionnalité dans le prochain aperçu (5.0 preview8).
Il n'est pas prévu de le porter en 3.x.

Solution pour asp net core 3.x :

var dic1 = new Dictionary<TKey, TValue>(); 
return Json(new { dic1 }); // does not work

var dic2 = from i in new Dictionary<TKey, TValue>() select new { i.Key, i.Value }
return Json(new { dic2 });  //works prety well

@verloka qui n'est pas la sortie souhaitée

@Jozkee , est-ce donc uniquement dans .NET 5 ou dans 3.* ?

Cela ne sera pas rétroporté vers 3.x, mais vous pouvez utiliser le package System.Text.Json NuGet dans votre projet pour obtenir toutes les nouvelles fonctionnalités de .NET 5.

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