Runtime: Novo Asp.NET Core 3.0 Json não serializa Dicionário<key/>

Criado em 7 ago. 2019  ·  51Comentários  ·  Fonte: dotnet/runtime

.NET Core 3.0 Preview 7

Asp.NET Web Apis, ao retornar um Dicionário, ele falha com uma NotSupportedException. Incluí a exceção abaixo.

Além disso, o método ControllerBase.BadRequest recebe um ModelStateDictionary , mas quando isso é retornado, o serializador explode também com uma NotSupportedException, mas uma mensagem ligeiramente diferente.

Quando esse suporte será adicionado? Como isso foi suportado no Json.net e em outros serializadores por um tempo, espero que esteja no radar.

Agradeço o fato de poder voltar a usar o Json.net, então muito obrigado por isso!

Exceção ao retornar um Dicionário
System.NotSupportedException: O tipo de coleção 'System.Collections.Generic.Dictionary`2 [System.Int32, System.String]' não é compatível.
em System.Text.Json.JsonClassInfo.GetElementType (Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.CreateProperty (Tipo declaradoPropertyType, Tipo runtimePropertyType, PropertyInfo propertyInfo, Tipo parentClassType, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.AddProperty (Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.AddPolicyProperty (Digite propertyType, opções JsonSerializerOptions)
em System.Text.Json.JsonClassInfo..ctor (tipo de tipo, opções de JsonSerializerOptions)
em System.Text.Json.JsonSerializerOptions.GetOrAddClass (Type classType)
em System.Text.Json.WriteStackFrame.Initialize (tipo de tipo, opções de JsonSerializerOptions)
em System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, valor do objeto, tipo de tipo, opções JsonSerializerOptions, CancelamentoToken cancellationToken)
em Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Codificação selectedEncoding)
em Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Codificação selectedEncoding)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (invocador ResourceInvoker, resultado IActionResult)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexto ResultExecutedContextSealed)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Fim do rastreamento de pilha do local anterior onde a exceção foi lançada ---
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (invocador ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (invocador ResourceInvoker)
em Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (Endpoint endpoint, Task requestTask, logger ILogger)
em Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexto HttpContext)
em Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexto HttpContext)
em Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexto HttpContext)
em Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
em Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
em Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexto HttpContext)

Exceção ao retornar BadRequest
System.NotSupportedException: O tipo de coleção 'Microsoft.AspNetCore.Mvc.SerializableError' não é compatível.
em System.Text.Json.JsonClassInfo.GetElementType (Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.CreateProperty (Tipo declaradoPropertyType, Tipo runtimePropertyType, PropertyInfo propertyInfo, Tipo parentClassType, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.AddProperty (Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
em System.Text.Json.JsonClassInfo.AddPolicyProperty (Digite propertyType, opções JsonSerializerOptions)
em System.Text.Json.JsonClassInfo..ctor (tipo de tipo, opções de JsonSerializerOptions)
em System.Text.Json.JsonSerializerOptions.GetOrAddClass (Type classType)
em System.Text.Json.WriteStackFrame.Initialize (tipo de tipo, opções de JsonSerializerOptions)
em System.Text.Json.JsonSerializer.WriteAsyncCore (Stream utf8Json, valor do objeto, tipo de tipo, opções JsonSerializerOptions, CancelamentoToken cancellationToken)
em Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Codificação selectedEncoding)
em Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (contexto OutputFormatterWriteContext, Codificação selectedEncoding)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (invocador ResourceInvoker, resultado IActionResult)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (contexto ResultExecutedContextSealed)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Fim do rastreamento de pilha do local anterior onde a exceção foi lançada ---
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (invocador ResourceInvoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
em Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (invocador ResourceInvoker)
em Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (Endpoint endpoint, Task requestTask, logger ILogger)
em Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (contexto HttpContext)
em Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (contexto HttpContext)
em Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (contexto HttpContext)
em Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
em Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
em Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexto HttpContext)

area-System.Text.Json enhancement

Comentários muito úteis

Para os recém-chegados, a solução temporária é reverter para Newtonsoft.Json .

  1. Adicione a referência do pacote a Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Adicione .AddNewtonsoftJson() logo após .AddControllers() / .AddMvc() ou qualquer outra combinação.

Todos 51 comentários

Ambos os erros de "exceção não suportada" são limitações dentro do serializador embutido e são por design (pelo menos para o que é enviado no 3.0).

Quando esse suporte será adicionado? Como isso foi suportado no Json.net e em outros serializadores por um tempo, espero que esteja no radar.
Agradeço o fato de poder voltar a usar o Json.net, então muito obrigado por isso!

Existem vários recursos de serializador que estão em nosso radar para oferecer suporte no vNext (então 5.0) e além, com suporte de dicionário personalizado sendo um deles.

Asp.NET Web Apis, ao retornar um Dicionário, ele falha com uma NotSupportedException

Ao serializar, apenas Dictionary<string, TValue> é suportado hoje (ou seja, TKeys que são strings). Seu dicionário é de <int, string> que não é compatível.
https://github.com/dotnet/corefx/blob/93d7aa1c1737b6da29d04b78557215e18eb786d4/src/System.Text.Json/tests/Serialization/DictionaryTests.cs#L385 -L390

@steveharter , @layomia - existe uma solução alternativa em potencial aqui nesse meio tempo? O que seria necessário para adicionar suporte para dicionário não codificado por string dentro do serializador para 5.0?

System.NotSupportedException: O tipo de coleção 'Microsoft.AspNetCore.Mvc.SerializableError' não é compatível.

~ Adicionar suporte para um tipo como SerializableError não estava no meu radar. @pranavkm , @rynowak - qual é o contexto aqui? Não estou familiarizado com ModelStateDictionary , isso poderia ser compatível com o próprio mvc com um conversor personalizado? ~

Edit: Nevermind, isso já foi corrigido.

System.NotSupportedException: O tipo de coleção 'Microsoft.AspNetCore.Mvc.SerializableError' não é compatível.

Este era um problema conhecido https://github.com/aspnet/AspNetCore/issues/11459 que foi corrigido recentemente (como parte da visualização 8): https://github.com/dotnet/corefx/pull/39001

Muito obrigado por suas respostas rápidas @ahsonkhan!

A "limitação" de a chave ser uma string realmente faz sentido quando penso nisso. Vejo agora que o Json.net realmente gera json com a chave sendo uma string, quando a desserialização me traria um int de volta. Seria definitivamente bom ter o suporte para chaves que não sejam string no futuro, mas não um obstáculo.

Ok, fico feliz em saber que o Mvc.SerializableError sem suporte foi corrigido. Alguma idéia sobre se há uma previsão de data de lançamento do Preview 8? Tentei pesquisar e encontrar algo, mas não vi nada sobre isso.

Assim que a visualização 8 for lançada, tentaremos dar a biblioteca de serialização json .net core 3 uma tentativa novamente, mas por enquanto precisamos continuar com Json.net

@steveharter , @layomia - existe uma solução alternativa em potencial aqui nesse meio tempo? O que seria necessário para adicionar suporte para dicionário não codificado por string dentro do serializador para 5.0?

>

@ahsonkhan @ willyt150 a solução alternativa para isso é usar um conversor personalizado que implemente JsonConverter<T> onde T é Dictionary<int, string> .
Consulte https://github.com/dotnet/corefx/issues/36639#issue -429928740 para alguns exemplos.

Alguma idéia sobre se há uma previsão de data de lançamento do Preview 8?

Algum tempo depois deste mês.

Pensando mais um pouco sobre isso, removendo os itens disponíveis por enquanto, pois pode ser algo que não queremos oferecer suporte por padrão.

Obrigado @layomia vou dar uma olhada nisso.

Obrigado @ahsonkhan , ansioso pela correção que está chegando!

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

Olá. Quando tento serializar o Dicionário com chave inteira, ele lança System.NotSupportedException.

Acho que faz sentido oferecer suporte à serialização Json, cujo dicionário tem a chave ToString -able. por exemplo, quando executamos ToString para int ou boolean, ele retorna "123" ou "true". Acho que essa chave é ToString -chave de tabela.

Verison

System.Text.Json Nuget Versão: 4.6.0-preview8.19405.3

Código

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

Esperado

"{"5": "five"}"

Mas o que aconteceu

Erro System.NotSupportedException lançado

Na verdade, há um problema de compatibilidade quando eu altero Newtonsoft.Json para System.Text.Json. Eles retornam string como eu esperava. Acho que System.Text.Json não precisa ser compatível, mas ... você sabe.

Implementei um conversor que suporta serialização e desserialização para IDictionary<TKey, TValue> onde TKey tem um 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;
        }
    }
}

Teste:

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

No entanto, ele ainda não pode serializar um dicionário aninhado como Dictionary<int, Dictionary<int, int>> porque System.Text.Json não aceita o tipo interno Dictionary<int, int> . Acho que é um bug.

No entanto, ainda não é possível serializar um dicionário aninhado, como um Dicionário> porque System.Text.Json não aceita o tipo interno Dicionário. Acho que é um bug.

O suporte a apenas <string, x> deve-se ao projeto devido ao corte do escopo para envio para a v3.0. A versão 3.0 pretende ser um produto mínimo viável com os cenários mais comuns suportados.

@steveharter Pelo menos você não deve lançar uma exceção notsupport quando houver um conversor utilizável.

Há algum plano para oferecer suporte a isso no .net core 3.1?

Para os recém-chegados, a solução temporária é reverter para Newtonsoft.Json .

  1. Adicione a referência do pacote a Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Adicione .AddNewtonsoftJson() logo após .AddControllers() / .AddMvc() ou qualquer outra combinação.

@steveharter Pelo menos você não deve lançar uma exceção notsupport quando houver um conversor utilizável.

Sim, esse é um ponto justo. Talvez possamos remover essa restrição para 3.1. cc @layomia

Também apenas para esclarecer que hoje os elementos do dicionário são serializados como propriedades, o que é possível porque a chave é uma string. O suporte a chaves não string significa que os elementos serão serializados como um KeyValuePair.

Puxa, tive esse problema logo depois de fazer a atualização para o 3.0.

Tive que instalar o pacote newton com AddNewtonsoftJson.

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

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

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

Essa serialização simples é bem tratada pela antiga biblioteca Newtonsoft json padrão, serializando a chave int como string. Em System.Text.Json, é lançada uma exceção sem suporte.

@israellot , @unruledboy e outros no tópico, você pode fornecer detalhes sobre por que seu modelo de objeto requer dicionários com chaves inteiras em seus cenários e por que alterá-lo para Dictionary<string, TValue> não funcionaria? Eu adoraria ver alguns usos para reunir requisitos e ajudar a motivar a correção.

Eles seriam serializados como strings de qualquer maneira, então não entendo em quais cenários você gostaria que seu dicionário subjacente tivesse chaves int32 em vez disso.

@ahsonkhan Eu acredito que a principal motivação é a compatibilidade.
O serializador padrão anterior era Newtonsoft, então os usuários podem ter escrito modelos inteiros contando com sua capacidade de serializar e desserializar classes arbitrárias. Migrar do 2.X para o 3.0 agora causará uma mudança silenciosa, já que saberemos apenas a incompatibilidade em tempo de execução.

Acredito que muitos cenários envolvem o uso de json apenas como transporte através do fio e, neste caso, o modelo de domínio pode ser Dicionário. Sua sugestão se resume a criar um Dicionário de objetos DTO separadoe a conversão entre os dois parece bastante ineficiente, pois agora precisamos alocar outro objeto apenas para ser compatível com o serializador.
Olhando estritamente para o serializador, restringir o dicionário a ter chaves de string é lógico, pois essa é a única representação json possível. Mas, considerando que o serializador desempenha um papel nos aplicativos, acredito que seja melhor remover o máximo de atrito possível.

Eu tive esse problema com um pequeno programa que estou escrevendo, no qual partes de um rótulo de versão são fornecidas por meio de um arquivo json.
As partes do rótulo possuem uma chave que especifica o índice onde a parte do rótulo pode ser inserida. Isso significa que as chaves são valores numéricos, por exemplo

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

Usando o Newtonsoft, o json pode ser desserializado sem problemas para um Dictionary<int, string> . Após a conversão para System.Text.Json, a serialização falhou.

Resolvi esse problema criando meu próprio DictionaryConverter e DictionaryConverter .
Eu também criei um conversor simples que permite que inteiros sejam desserializados de uma string

Em seguida, eles são registrados por meio das opções do serializador: https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22

Essas alterações permitem que as chaves de um dicionário sejam desserializadas em vez de lidas diretamente como uma string. Isso abre ainda mais o suporte para chaves de tipos arbitrários que podem ter seus próprios conversores registrados para serialização (por exemplo, enums / tipo / tipos que podem ser serializados como chaves exclusivas, etc.)

Não testei formalmente as coisas, mas no desenvolvimento atual isso parece ter resolvido o problema.

Definir o marco para 3.1 para remover quaisquer restrições que impeçam a criação de um conversor personalizado que possa lidar com qualquer TKey para Dictionary<TKey,TValue> .

Update: Eu adicionei amostras que funcionam com 3.0. Não notei nenhum problema, como com dicionários aninhados, conforme relatado acima.

Existem dois formatos JSON sendo usados ​​nas amostras:

  • Objeto JSON com propriedades de string: {"1":"val1","2":"val2"} mesmo que TKey não seja uma string.

    • Eles dependem de um método TryParse no tipo de chave compatível correspondente. Para a v3.0, não é viável fornecer suporte para TKey generalizado, pois TryParse métodos podem ser diferentes para qualquer TKey e porque há configurações de cultura que precisam ser fornecidas (normalmente invariante). Portanto, os exemplos abaixo são para um único tipo TKey .

    • Sample Dictionary<int, string> . Este é um exemplo simples para apenas TValue == string .

    • Sample Dictionary<Guid, TValue> . Isso pode lidar com qualquer TValue .

    • Sample Dictionary<TKey, TValue> where TKey is Enum . Para Enums; isso pode lidar com qualquer TValue .

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

Se esses exemplos forem satisfatórios, mudarei esse problema para 5.0 a fim de discutir se oferecemos suporte integrado que não exija conversores personalizados.

Definir o marco como 5.0 para consideração (e se algum dos exemplos acima funcionar por padrão).

A desserialização também parece mapear tipos de objetos genéricos para JsonDocument em vez de seus tipos normais (primitivos?).

Exemplo:

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"] é mostrado como: ValueKind = Number: "86"
NewtonSoftJson [0] ["id"] mostra como: 86

@steveharter Um dicionário enum com esse conversor serializa como:
{ "Stuff": [ { "Key": 1, "Value": "String" }, { "Key": 3, "Value": "String" }, { "Key": 2, "Value": "String" } ] }

Embora o JSON.NET forneça o que eu presumo que a maioria das pessoas esperaria:
{ "Stuff": { "Item1": "String", "Item2": "String", "Item3": "String" }

A desserialização também parece mapear tipos de objetos genéricos para JsonDocument em vez de seus tipos normais (primitivos?).

Sim, é por design. Veja https://github.com/dotnet/corefx/issues/38713

@steveharter Um dicionário enum com esse conversor serializa como:
{"Coisas": [{"Chave": 1, "Valor": "Cadeia"}, {"Chave": 3, "Valor": "Cadeia"}, {"Chave": 2, "Valor": " Fragmento" } ] }
Embora o JSON.NET forneça o que eu presumo que a maioria das pessoas esperaria:
{"Coisas": {"Item1": "String", "Item2": "String", "Item3": "String"}

Presumo que você esteja usando o "Dicionário de Amostra"? Em caso afirmativo, sim, isso usou KeyValuePair que tem propriedades" Key "e" Value ". Não forneci uma amostra para dicionários enum baseados em TKey que serializam como nomes de propriedade, mas irei trabalhar para adicionar isso.

Sim, aquele. E, ok, achei que você quisesse dizer isso como um serializador de dicionário genérico.
Interessado em ver sua nova amostra quando disponível, pois a que estamos usando no momento não parece tão rápida quanto gostaria.

@roguecode aqui está um exemplo de Enum para Dictionary<TKey, TValue> que TKey é um Enum e usa a sintaxe JSON de "propriedade" em vez de KeyValuePair. Eu também atualizei a lista de amostras acima para incluir esta nova amostra.

Olá, tenho algo semelhante, mas diferente, e gostaria de saber se você pode me indicar onde mais procurar.

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

Inicialização rodando como 3.0:

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

Se eu tiver 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"

Em 2.2, a execução de Newtonsoft.Json.JsonConvert.SerializeObject(problemDetails) retorna

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

No 3.0 ele retorna:

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

A string serializada da versão 3.0 inclui o IDictionarynome da propriedade, Extensions , e podemos desserializar corretamente essa string no 3.0. Você pode ver que esse nome de propriedade foi omitido na versão 2.x.

O problema é que a serialização 3.0 que ocorre quando a resposta é retornada de um filtro usando um BadRequestObjectResult , do código abaixo:

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

..., o conteúdo da resposta que é retornado é da mesma forma que a versão 2.2 (o nome da propriedade Extensions é excluído), o que faz com que a propriedade Extensions seja desserializada como uma coleção vazia (usando Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationProblemDetails>() )

De alguma forma, essa serialização não está usando a mesma serialização da biblioteca Newtonsoft com a qual estamos tentando desserializar.

Obrigado pela consideração!

Eu tenho algo parecido mas diferente

@ ts46235 poderia abrir um novo problema para este, visto que é diferente do problema atual?

@ ts46235 Respondi à sua pergunta na outra edição que você abriu aqui - https://github.com/aspnet/AspNetCore/issues/16618. Marcando a conversa aqui como fora do assunto.

atualizado para o Core 3.1 e ainda não corrigido

Acabei de atualizar para 3.1 e fui atingido com isso. De volta ao JSON.NET, vou ... (eu uso as teclas GUID)

Dot net core 3.1
Dicionáriotambém não está funcionando, mesmo o objeto na chave é, na verdade, uma string

Eu também acabei de acertar isso, e que limitação silenciosa e assustadora é, já que, como outros apontaram, você não verá isso em tempo de compilação. No meu caso, quero serializar Dictionary<int, List<string>> , o que não me parece particularmente exótico.

Eles deveriam consertá-lo, mas vejo isso uma e outra vez, mesmo com o formatador antigo, formatador binário newtsoft, dicionários em dicionários, dicionários com interfaces. Eles deveriam consertar, mas se você não quer problemas, as pessoas realmente não deveriam colocar objetos complexos como Dicionários em contratos de serialização que você está pedindo problemas - a newtsoft estragou as pessoas. Observe todas as propriedades públicas na contagem do Dicionário etc, você está contando com algo personalizado no serializador para mapear isso.

Infelizmente, não existe um tipo simples para isso em C # para nomes de propriedades, portanto, o Dicionário é forçado. Então, eu só estou triste ..

Aqui está uma solução alternativa, mas de forma alguma uma solução 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
        };
    }

Talvez o suporte para (alguns dos) tipos comuns de serializadores possa ser adicionado OOTB, por exemplo, esta lista , e também se um desses tipos .IsAssignableFrom(systemDotObjectInstance.GetType()) suportar Dictionary<<ins i="7">object</ins>, V> .

Aqui está a proposta para adicionar suporte a tipos não string TKey em dicionários.
https://github.com/dotnet/runtime/pull/32676

Por favor, deixe-nos saber quaisquer pensamentos ou preocupações.

Especificamente, forneça feedback sobre o conjunto de tipos que @Jozkee planeja oferecer suporte para chaves de dicionário, especialmente se você precisar que outros tipos sejam suportados (basicamente, todos os tipos numéricos primitivos integrados, enums e alguns outros):
https://github.com/dotnet/runtime/blob/a5d96c0e280b56412d4614848f5ee3b1e0d7f216/src/libraries/System.Text.Json/docs/KeyConverter_spec.md#keyconverter

Dicionáriotambém não está funcionando, mesmo o objeto na chave é, na verdade, uma string

@ andrew-vdb, o suporte à serialização de chaves de objetos arbitrários provavelmente permanecerá sem suporte. No entanto, se o tipo de tempo de execução das chaves de objeto for um dos tipos "recentemente suportados", a serialização funcionará para isso assim que o recurso for concluído. A desserialização, no entanto, permanecerá como JsonElement in a box (até que este problema relacionado para optar pelo comportamento diferente seja resolvido): https://github.com/dotnet/runtime/issues/29960

@Jozkee quais são os tipos habilitados para TValue ? Presumivelmente, qualquer coisa que você possa serializar atualmente como um objeto autônomo?

Presumivelmente, qualquer coisa que você possa serializar atualmente como um objeto autônomo?

sim.

A desserialização também parece mapear tipos de objetos genéricos para JsonDocument em vez de seus tipos normais (primitivos?).

Exemplo:

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"] é mostrado como: ValueKind = Number: "86"
NewtonSoftJson [0] ["id"] mostra como: 86

De todos os problemas mencionados, esse é o que mais me incomoda. Listaou T [] ou Dicionáriodeve desserializar adequadamente para qualquer tipo que possa ser mapeado diretamente dos poucos tipos que o Json possui para os tipos CLR.

A versão 3.0 pretende ser um produto mínimo viável com os cenários mais comuns suportados

Eu me pergunto como List> não é um dos cenários mais comuns:

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

Como isso também desserializa em System.Text.JsonElement, onde eu esperaria double (Number) e string (String)

Eu tive esse problema com um pequeno programa que estou escrevendo, no qual partes de um rótulo de versão são fornecidas por meio de um arquivo json.
As partes do rótulo possuem uma chave que especifica o índice onde a parte do rótulo pode ser inserida. Isso significa que as chaves são valores numéricos, por exemplo

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

Usando o Newtonsoft, o json pode ser desserializado sem problemas para um Dictionary<int, string> . Após a conversão para System.Text.Json, a serialização falhou.

Resolvi esse problema criando meu próprio DictionaryConverter e DictionaryConverter .
Eu também criei um conversor simples que permite que inteiros sejam desserializados de uma string

Eles são então registrados por meio das opções do serializador: https://github.com/Kieranties/SimpleVersion/blob/feature/netcore3/src/SimpleVersion.Core/Serialization/Serializer.cs#L20

Essas alterações permitem que as chaves de um dicionário sejam desserializadas em vez de lidas diretamente como uma string. Isso abre ainda mais o suporte para chaves de tipos arbitrários que podem ter seus próprios conversores registrados para serialização (por exemplo, enums / tipo / tipos que podem ser serializados como chaves exclusivas, etc.)

Não testei formalmente as coisas, mas no desenvolvimento atual isso parece ter resolvido o problema.

ei @Kieranties , o github tem links 404 para mim

ei @Kieranties , o github tem links 404 para mim

@AirEssY Corrigi os links em meu comentário original, acho que agora estão no mestre em: https://github.com/Kieranties/SimpleVersion/tree/master/src/SimpleVersion.Core/Serialization

Se o Dicionário for equivalente a um Mapa JavaScript, então qualquer (tipo JS representado em C #) deve ser aceitável,

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).

Um exemplo da abordagem padrão para desserializar um mapa em JS é:

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

console.log(countries)

Que produz:

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

TL; DR: restringir chaves a strings não funciona bem com JS

@Jozkee, então, ele virá apenas no .NET 5 ou será no 3. *?

@onionhammer .NET 5.0, você também pode experimentar o recurso na próxima visualização (5.0 preview8).
Não há planos de portar isso para 3.x.

Solução 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 essa não é a saída desejada

@Jozkee, então, ele virá apenas no .NET 5 ou será no 3. *?

Isso não será feito backport para 3.x, mas você pode adicionar o uso do pacote System.Text.Json NuGet em seu projeto para obter todos os novos recursos do .NET 5.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

GitAntoinee picture GitAntoinee  ·  3Comentários

yahorsi picture yahorsi  ·  3Comentários

aggieben picture aggieben  ·  3Comentários

sahithreddyk picture sahithreddyk  ·  3Comentários

Timovzl picture Timovzl  ·  3Comentários