Runtime: El nuevo Asp.NET Core 3.0 Json no serializa el diccionario<key/>

Creado en 7 ago. 2019  ·  51Comentarios  ·  Fuente: dotnet/runtime

.NET Core 3.0 Preview 7

Asp.NET Web Apis, al devolver un diccionario falla con una NotSupportedException. He incluido la excepción a continuación.

Además, el método ControllerBase.BadRequest toma un ModelStateDictionary , pero cuando se devuelve, el serializador también explota con una NotSupportedException, pero con un mensaje ligeramente diferente.

¿Cuándo se agregará este soporte? Dado que esto ha sido compatible con Json.net y otros serializadores durante un tiempo, espero que esté en el radar.

Aprecio el hecho de que puedo volver a habilitar el uso de Json.net, ¡así que muchas gracias por eso!

Excepción al devolver un diccionario
System.NotSupportedException: el tipo de colección 'System.Collections.Generic.Dictionary`2 [System.Int32, System.String]' no es compatible.
en System.Text.Json.JsonClassInfo.GetElementType (Tipo propertyType, Tipo parentType, MemberInfo memberInfo, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.CreateProperty (Tipo declaradoPropertyType, Tipo runtimePropertyType, PropertyInfo propertyInfo, Tipo parentClassType, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.AddProperty (Tipo propertyType, PropertyInfo propertyInfo, Tipo classType, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.AddPolicyProperty (Escriba propertyType, opciones de JsonSerializerOptions)
en System.Text.Json.JsonClassInfo..ctor (Tipo de tipo, opciones de JsonSerializerOptions)
en System.Text.Json.JsonSerializerOptions.GetOrAddClass (Tipo classType)
en System.Text.Json.WriteStackFrame.Initialize (Tipo de tipo, opciones de JsonSerializerOptions)
en System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Valor de objeto, Tipo de tipo, Opciones de JsonSerializerOptions, CancellationToken cancellationToken)
en Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Encoding selectedEncoding)
en Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Encoding selectedEncoding)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (invocador ResourceInvoker, resultado IActionResult)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexto ResultExecutedContextSealed)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (invocador ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (invocador ResourceInvoker)
en Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (punto final, tarea requestTask, registrador ILogger)
en Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexto HttpContext)
en Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexto HttpContext)
en Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexto HttpContext)
en Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
en Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
en Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexto HttpContext)

Excepción al devolver BadRequest
System.NotSupportedException: el tipo de colección 'Microsoft.AspNetCore.Mvc.SerializableError' no es compatible.
en System.Text.Json.JsonClassInfo.GetElementType (Tipo propertyType, Tipo parentType, MemberInfo memberInfo, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.CreateProperty (Tipo declaradoPropertyType, Tipo runtimePropertyType, PropertyInfo propertyInfo, Tipo parentClassType, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.AddProperty (Tipo propertyType, PropertyInfo propertyInfo, Tipo classType, opciones JsonSerializerOptions)
en System.Text.Json.JsonClassInfo.AddPolicyProperty (Escriba propertyType, opciones de JsonSerializerOptions)
en System.Text.Json.JsonClassInfo..ctor (Tipo de tipo, opciones de JsonSerializerOptions)
en System.Text.Json.JsonSerializerOptions.GetOrAddClass (Tipo classType)
en System.Text.Json.WriteStackFrame.Initialize (Tipo de tipo, opciones de JsonSerializerOptions)
en System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, Valor de objeto, Tipo de tipo, Opciones de JsonSerializerOptions, CancellationToken cancellationToken)
en Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Encoding selectedEncoding)
en Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Encoding selectedEncoding)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (invocador ResourceInvoker, resultado IActionResult)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexto ResultExecutedContextSealed)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (invocador ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
en Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (invocador ResourceInvoker)
en Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (punto final, tarea requestTask, registrador ILogger)
en Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexto HttpContext)
en Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexto HttpContext)
en Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexto HttpContext)
en Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
en Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
en Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexto HttpContext)

area-System.Text.Json enhancement

Comentario más útil

Para los recién llegados, la solución temporal es volver a Newtonsoft.Json .

  1. Agregue la referencia del paquete a Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Agregue .AddNewtonsoftJson() justo después de .AddControllers() / .AddMvc() o cualquier otra combinación.

Todos 51 comentarios

Ambos errores de "excepción no admitida" son limitaciones dentro del serializador incorporado y son por diseño (al menos para lo que se envía en 3.0).

¿Cuándo se agregará este soporte? Dado que esto ha sido compatible con Json.net y otros serializadores durante un tiempo, espero que esté en el radar.
Aprecio el hecho de que puedo volver a habilitar el uso de Json.net, ¡así que muchas gracias por eso!

Hay un montón de capacidades de serializador que están en nuestro radar para ser compatibles con vNext (por lo tanto, 5.0) y más allá, y la compatibilidad con el diccionario personalizado es una de ellas.

Asp.NET Web Apis, al devolver un diccionario falla con una excepción NotSupportedException

Al serializar, actualmente solo se admite Dictionary<string, TValue> (es decir, TKeys que son cadenas). Su diccionario es de <int, string> que no es compatible.
https://github.com/dotnet/corefx/blob/93d7aa1c1737b6da29d04b78557215e18eb786d4/src/System.Text.Json/tests/Serialization/DictionaryTests.cs#L385 -L390

@steveharter , @layomia : ¿hay alguna solución alternativa aquí mientras tanto? ¿Qué se necesitaría para agregar soporte para diccionario sin clave de cadena dentro del serializador para 5.0?

System.NotSupportedException: el tipo de colección 'Microsoft.AspNetCore.Mvc.SerializableError' no es compatible.

~ Agregar soporte para un tipo como SerializableError no estaba en mi radar. @pranavkm , @rynowak : ¿cuál es el contexto aquí? No estoy familiarizado con ModelStateDictionary , ¿podría ser compatible con mvc con un convertidor personalizado? ~

Editar: No importa, eso ya está arreglado.

System.NotSupportedException: el tipo de colección 'Microsoft.AspNetCore.Mvc.SerializableError' no es compatible.

Este fue un problema conocido https://github.com/aspnet/AspNetCore/issues/11459 que se solucionó recientemente (como parte de la vista previa 8): https://github.com/dotnet/corefx/pull/39001

¡Muchas gracias por tus rápidas respuestas @ahsonkhan!

La "limitación" de que la clave sea una cadena en realidad tiene sentido cuando lo pienso. Ahora veo que Json.net en realidad genera json con la clave como una cadena, cuando la deserialización me devolvería un int. Definitivamente sería bueno tener el soporte para claves que no sean de cadena en el futuro, pero no un impedimento para el espectáculo.

Ok, me alegra saber que se ha solucionado el error Mvc.SerializableError que no se admite. ¿Alguna idea sobre si hay una fecha de lanzamiento esperada de Preview 8? Intenté buscar y encontrar algo, pero no vi nada al respecto.

Una vez que salga la vista previa 8, intentaremos probar nuevamente la biblioteca de serialización json .net core 3, pero por ahora debemos seguir con Json.net

@steveharter , @layomia : ¿hay alguna solución alternativa aquí mientras tanto? ¿Qué se necesitaría para agregar soporte para diccionario sin clave de cadena dentro del serializador para 5.0?

>

@ahsonkhan @ willyt150 la solución para esto es usar un convertidor personalizado que implemente JsonConverter<T> donde T es Dictionary<int, string> .
Consulte https://github.com/dotnet/corefx/issues/36639#issue -429928740 para ver algunos ejemplos.

¿Alguna idea sobre si hay una fecha de lanzamiento esperada de Preview 8?

Algún tiempo a finales de este mes.

Pensando en esto un poco más, eliminando las opciones disponibles por ahora, ya que puede ser algo que no queremos admitir de forma predeterminada.

Gracias @layomia , echaré un vistazo a eso.

Gracias @ahsonkhan , ¡ llegue la solución!

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

Hola. Cuando intento serializar el diccionario con una clave entera, arroja System.NotSupportedException.

Creo que tiene sentido admitir la serialización de Json, cuyo diccionario tiene la clave ToString -able. por ejemplo, cuando ejecutamos ToString para int o boolean, devuelve "123" o "true". Creo que la clave es ToString -able key.

Verison

System.Text.Json Nuget Versión: 4.6.0-preview8.19405.3

Código

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

Esperado

"{"5": "five"}"

Pero que pasa

Error System.NotSupportedException arrojado

En realidad, hay un problema de compatibilidad cuando cambio Newtonsoft.Json a System.Text.Json. Devuelven cadena como esperaba. Creo que System.Text.Json no tiene que ser compatible pero ... ya sabes.

He implementado un convertidor que admite tanto la serialización como la deserialización para IDictionary<TKey, TValue> donde TKey tiene un método estático 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;
        }
    }
}

Prueba:

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));
    }
}

Resultado:

{"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"}}}

Sin embargo, todavía no puede serializar un diccionario anidado como Dictionary<int, Dictionary<int, int>> porque System.Text.Json no acepta el tipo interno Dictionary<int, int> . Creo que es un error.

Sin embargo, todavía no puede serializar un diccionario anidado como Dictionary> porque System.Text.Json no acepta el diccionario de tipo interno. Creo que es un error.

Admitir solo <string, x> es por diseño debido al recorte de alcance para enviarlo para v3.0. La versión 3.0 está destinada a ser un producto viable mínimo con los escenarios más comunes compatibles.

@steveharter Al menos no debería lanzar una excepción notsupporte cuando hay un convertidor utilizable.

¿Hay planes para admitir esto en .net core 3.1?

Para los recién llegados, la solución temporal es volver a Newtonsoft.Json .

  1. Agregue la referencia del paquete a Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Agregue .AddNewtonsoftJson() justo después de .AddControllers() / .AddMvc() o cualquier otra combinación.

@steveharter Al menos no debería lanzar una excepción notsupporte cuando hay un convertidor utilizable.

Sí, ese es un punto justo. Quizás podamos eliminar esta restricción para 3.1. cc @layomia

También solo para aclarar que hoy en día los elementos del diccionario se serializan como propiedades, lo cual es posible porque la clave es una cadena. La compatibilidad con claves que no son cadenas significa que los elementos se serializarán como un KeyValuePair.

Vaya, tuve este problema justo después de actualizar a 3.0.

Tuve que instalar el paquete newton con AddNewtonsoftJson.

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

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

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

Esta simple serialización es manejada bien por la antigua biblioteca json predeterminada de Newtonsoft al serializar la clave int como cadena. En System.Text.Json se lanza una excepción no admitida.

@israellot , @unruledboy y otros en el hilo, ¿pueden proporcionar detalles sobre por qué su modelo de objetos requiere diccionarios con claves enteras en sus escenarios y por qué cambiarlo a Dictionary<string, TValue> no funcionaría? Me encantaría ver algunos usos para recopilar requisitos y ayudar a motivar la solución.

De todos modos, se serializarían como cadenas, por lo que no entiendo en qué escenarios le gustaría que su diccionario subyacente tuviera claves int32 en su lugar.

@ahsonkhan Creo que la motivación clave es la compatibilidad.
El serializador predeterminado anterior era Newtonsoft, por lo que los usuarios pueden haber escrito modelos completos confiando en su capacidad para serializar y deserializar clases arbitrarias. La migración de 2.X a 3.0 ahora provocará un cambio rotundo y silencioso, ya que solo conoceremos la incompatibilidad en tiempo de ejecución.

Creo que muchos escenarios implican el uso de json solo como transporte a través del cable, y en este caso, el modelo de dominio podría ser Dictionary. Su sugerencia se reduce a crear un diccionario de objetos DTO separadoy convertir entre los dos, parece bastante ineficiente ya que ahora necesitamos asignar otro objeto solo para cumplir con el serializador.
Mirando estrictamente al serializador, restringir el diccionario para tener claves de cadena es lógico, ya que esa es la única representación json posible. Pero teniendo en cuenta que el serializador juega un papel en las aplicaciones, creo que es mejor eliminar la mayor cantidad de fricción posible.

Me encontré con este problema con un pequeño programa que estoy escribiendo donde las partes de una etiqueta de versión se proporcionan a través de un archivo json.
Las partes de la etiqueta tienen una clave que especifica el índice donde se puede insertar la parte de la etiqueta. Esto significa que las claves son valores numéricos, p. Ej.

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

Usando Newtonsoft, el json se puede deserializar sin problemas a Dictionary<int, string> . Después de convertir a System.Text.Json, la serialización falló.

Resolví este problema creando mi propio DictionaryConverter y DictionaryConverter .
También creé un convertidor simple que permite deserializar enteros de una cadena

Luego, estos se registran a través de las opciones del serializador: https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22

Estos cambios permiten deserializar las claves de un diccionario en lugar de leerlas directamente como una cadena. Esto abre aún más el soporte para que las claves sean tipos arbitrarios que podrían tener sus propios convertidores registrados para la serialización (por ejemplo, enumeraciones / tipos / tipos que pueden serializarse como claves únicas, etc.)

No he probado cosas formalmente, pero dentro del desarrollo actual esto parece haber resuelto el problema.

Establecer hito para 3.1 para eliminar cualquier restricción que impida la creación de un convertidor personalizado que pueda manejar cualquier TKey por Dictionary<TKey,TValue> .

Actualización: he agregado muestras que funcionan con 3.0. No noté ningún problema, como con los diccionarios anidados, como se informó anteriormente.

Hay dos formatos JSON que se utilizan en las muestras:

  • Objeto JSON con propiedades de cadena: {"1":"val1","2":"val2"} aunque TKey no es una cadena.

    • Estos se basan en un método TryParse en el tipo de clave compatible correspondiente. Para v3.0 no es factible proporcionar soporte para un TKey generalizado ya que los métodos TryParse pueden ser diferentes para cualquier TKey y porque hay configuraciones de cultura que deben proporcionarse (típicamente invariante). Por tanto, los ejemplos siguientes son para un solo tipo TKey .

    • Sample Dictionary<int, string> . Este es un ejemplo simple por solo TValue == string .

    • Sample Dictionary<Guid, TValue> . Esto puede manejar cualquier TValue .

    • Sample Dictionary<TKey, TValue> where TKey is Enum . Para enumeraciones; esto puede manejar cualquier TValue .

  • Matriz JSON con entradas KeyValuePair: [{"Key":1,"Value":"val1"},{"Key":2,"Value":"val2"}]

Si estas muestras son satisfactorias, cambiaré este problema a 5.0 para analizar si proporcionamos soporte integrado que no requiere convertidores personalizados.

Establecer el hito en 5.0 para su consideración (si alguno de los ejemplos anteriores debería funcionar de forma predeterminada).

La deserialización también parece asignar tipos de objetos genéricos a JsonDocument en lugar de sus tipos normales (¿primitivos?).

Ejemplo:

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"] se muestra como: ValueKind = Número: "86"
NewtonSoftJson [0] ["id"] se muestra como: 86

@steveharter Un diccionario codificado enum con ese convertidor se serializa como:
{ "Stuff": [ { "Key": 1, "Value": "String" }, { "Key": 3, "Value": "String" }, { "Key": 2, "Value": "String" } ] }

Si bien JSON.NET ofrece lo que supongo que la mayoría de la gente esperaría:
{ "Stuff": { "Item1": "String", "Item2": "String", "Item3": "String" }

La deserialización también parece asignar tipos de objetos genéricos a JsonDocument en lugar de sus tipos normales (¿primitivos?).

Sí, eso es por diseño. Ver https://github.com/dotnet/corefx/issues/38713

@steveharter Un diccionario codificado enum con ese convertidor se serializa como:
{"Cosas": [{"Clave": 1, "Valor": "Cadena"}, {"Clave": 3, "Valor": "Cadena"}, {"Clave": 2, "Valor": " Cuerda" } ] }
Si bien JSON.NET ofrece lo que supongo que la mayoría de la gente esperaría:
{"Cosas": {"Elemento1": "Cadena", "Elemento2": "Cadena", "Elemento3": "Cadena"}

Supongo que estás usando el "Diccionario de muestra"? Si es así, sí que usó KeyValuePair que tiene propiedades" Clave "y" Valor ". No proporcioné una muestra para los diccionarios de enumeración basados ​​en TKey que se serializan como nombres de propiedad, pero trabajaré para agregar eso.

Sí, ese. Y bueno, pensé que lo habías dicho como un serializador de diccionario genérico.
Interesado en ver su nueva muestra cuando esté disponible, ya que la que estamos usando actualmente no parece tan rápida como me gustaría.

@roguecode aquí hay una muestra de Enum para Dictionary<TKey, TValue> donde TKey es un Enum y usa la sintaxis JSON de "propiedad" en lugar de KeyValuePair. También actualicé la lista de muestras anterior para incluir esta nueva muestra.

Hola, tengo algo similar pero diferente y me pregunto si me pueden indicar dónde más buscar.

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

Inicio que se ejecuta como 3.0:

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

Si tengo este objeto 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, la ejecución de Newtonsoft.Json.JsonConvert.SerializeObject(problemDetails) devuelve

{"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 devuelve:

{"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 cadena serializada de la versión 3.0 incluye el IDictionarynombre de propiedad, Extensions , y podemos deserializar correctamente esa cadena en 3.0. Puede ver que este nombre de propiedad no se incluye en la versión 2.x.

El problema es que la serialización 3.0 que se produce cuando la respuesta se devuelve desde un filtro usando un BadRequestObjectResult , del siguiente código:

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

..., el contenido de respuesta que se devuelve tiene el mismo formato que la versión 2.2 (se excluye el nombre de propiedad Extensions ), lo que hace que la propiedad Extensions deserialice como una colección vacía (usando Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationProblemDetails>() )

De alguna manera, esta serialización no usa la misma serialización que la biblioteca Newtonsoft con la que estamos tratando de deserializar.

¡Gracias por la consideración!

Tengo algo parecido pero diferente

@ ts46235 ¿ podría abrir un nuevo problema para esto, ya que es diferente del problema actual?

@ ts46235 Respondí a su pregunta en el otro número que abrió aquí: https://github.com/aspnet/AspNetCore/issues/16618. Marcando la conversación aquí como fuera de tema.

actualizado a Core 3.1 y aún no arreglado

Acabo de actualizar a 3.1 y me golpeó con esto. De vuelta a JSON.NET voy ... (uso claves GUID)

Núcleo de red de puntos 3.1
Diccionariotampoco funciona, incluso el objeto en la clave es en realidad una cadena

Yo también acabo de golpear esto, y qué limitación silenciosa y aterradora es, ya que, como otros han señalado, no verá esto en el momento de la compilación. En mi caso, quiero serializar Dictionary<int, List<string>> , lo que no me parece particularmente exótico.

Deberían arreglarlo, pero veo esto una y otra vez incluso con el formateador antiguo, el formateador binario del newtsoft temprano, los diccionarios en los diccionarios, los diccionarios con interfaces. Deberían solucionarlo, pero si no quiere problemas, la gente realmente no debería poner objetos complejos como diccionarios en los contratos de serialización y buscar problemas: newtsoft ha echado a perder a la gente. Mire todas las propiedades públicas en el recuento de diccionarios, etc., confía en algo personalizado en el serializador para asignar esto.

Desafortunadamente, no hay un tipo simple para esto en C # para los nombres de propiedad, por lo que el Diccionario es obligatorio. Así que estoy triste ...

Aquí hay una solución alternativa, pero de ninguna manera una solución completa:

   [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
        };
    }

Quizás se podría agregar soporte para (algunos de los) serializadores de tipos comunes OOTB, por ejemplo, esta lista , y también si uno de estos tipos .IsAssignableFrom(systemDotObjectInstance.GetType()) es compatible con Dictionary<<ins i="7">object</ins>, V> .

Aquí está la propuesta para agregar soporte a los tipos TKey que no son cadenas en los diccionarios.
https://github.com/dotnet/runtime/pull/32676

Háganos saber sus pensamientos o inquietudes.

Específicamente, proporcione comentarios sobre el conjunto de tipos que @Jozkee planea admitir para claves de diccionario, especialmente si necesita que se admitan otros tipos (básicamente, todos los tipos numéricos primitivos integrados, enumeraciones y algunos otros):
https://github.com/dotnet/runtime/blob/a5d96c0e280b56412d4614848f5ee3b1e0d7f216/src/libraries/System.Text.Json/docs/KeyConverter_spec.md#keyconverter

Diccionariotampoco funciona, incluso el objeto en la clave es en realidad una cadena

@ andrew-vdb, es probable que no se admita la serialización de claves de objetos arbitrarios. Sin embargo, si el tipo de tiempo de ejecución de las claves de objeto es uno de los tipos "recientemente admitidos", la serialización funcionará para eso una vez que la función esté lista. Sin embargo, la deserialización permanecerá como JsonElement en caja (hasta que se aborde este problema relacionado para optar por el comportamiento diferente): https://github.com/dotnet/runtime/issues/29960

@Jozkee ¿cuáles son los tipos habilitados para TValue ? Presumiblemente, ¿algo que pueda serializar actualmente como un objeto independiente?

Presumiblemente, ¿algo que pueda serializar actualmente como un objeto independiente?

Si.

La deserialización también parece asignar tipos de objetos genéricos a JsonDocument en lugar de sus tipos normales (¿primitivos?).

Ejemplo:

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"] se muestra como: ValueKind = Número: "86"
NewtonSoftJson [0] ["id"] se muestra como: 86

De todos los problemas mencionados, este es el que más me molesta. Listao T [] o Diccionariodebe deserializarse correctamente para cualquier tipo que pueda asignarse directamente desde los pocos tipos que tiene Json a los tipos CLR.

La versión 3.0 está destinada a ser un producto viable mínimo con los escenarios más comunes compatibles

Me pregunto como List> no es uno de los escenarios más comunes:

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

Como esto también se deserializa en System.Text.JsonElement, donde esperaría doble (Número) y cadena (Cadena)

Me encontré con este problema con un pequeño programa que estoy escribiendo donde las partes de una etiqueta de versión se proporcionan a través de un archivo json.
Las partes de la etiqueta tienen una clave que especifica el índice donde se puede insertar la parte de la etiqueta. Esto significa que las claves son valores numéricos, p. Ej.

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

Usando Newtonsoft, el json se puede deserializar sin problemas a Dictionary<int, string> . Después de convertir a System.Text.Json, la serialización falló.

Resolví este problema creando mi propio DictionaryConverter y DictionaryConverter .
También creé un convertidor simple que permite deserializar enteros de una cadena

Luego, estos se registran a través de las opciones del serializador: https://github.com/Kieranties/SimpleVersion/blob/feature/netcore3/src/SimpleVersion.Core/Serialization/Serializer.cs#L20

Estos cambios permiten deserializar las claves de un diccionario en lugar de leerlas directamente como una cadena. Esto abre aún más el soporte para que las claves sean tipos arbitrarios que podrían tener sus propios convertidores registrados para la serialización (por ejemplo, enumeraciones / tipos / tipos que pueden serializarse como claves únicas, etc.)

No he probado cosas formalmente, pero dentro del desarrollo actual esto parece haber resuelto el problema.

Hola @Kieranties , el github enlaza 404 para mí

Hola @Kieranties , el github enlaza 404 para mí

@AirEssY He corregido los enlaces en mi comentario original, creo que ahora están en maestro en: https://github.com/Kieranties/SimpleVersion/tree/master/src/SimpleVersion.Core/Serialization

Si el diccionario es equivalente a un mapa de JavaScript, entonces cualquier (tipo JS representado en C #) debería ser aceptable,

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 ejemplo del enfoque estándar para deserializar un mapa en JS es:

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

console.log(countries)

Que produce:

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

TL; DR: restringir las claves a las cadenas no funciona bien con JS

@Jozkee, ¿vendrá esto solo en .NET 5 o se incluirá en 3. *?

@onionhammer .NET 5.0, también puede probar la función en la siguiente vista previa (5.0 vista previa8).
No hay planes de migrar esto a 3.x.

Solución para 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 que no es la salida deseada

@Jozkee, ¿vendrá esto solo en .NET 5 o se incluirá en 3. *?

Esto no se exportará a 3.x, pero puede agregar el paquete System.Text.Json NuGet en su proyecto para obtener todas las funciones nuevas en .NET 5.

¿Fue útil esta página
0 / 5 - 0 calificaciones