Runtime: Новый Asp.NET Core 3.0 Json не сериализует словарь<key/>

Созданный на 7 авг. 2019  ·  51Комментарии  ·  Источник: dotnet/runtime

.NET Core 3.0, предварительная версия 7

Asp.NET Web Apis, при возврате словаря происходит сбой NotSupportedException. Я включил исключение ниже.

Кроме того, метод ControllerBase.BadRequest принимает ModelStateDictionary , но когда он возвращается, сериализатор также взрывается с NotSupportedException, но с немного другим сообщением.

Когда будет добавлена ​​эта поддержка? Поскольку это некоторое время поддерживается в Json.net и других сериализаторах, я надеюсь, что это на радаре.

Я ценю тот факт, что могу снова отказаться от использования Json.net, поэтому большое вам за это спасибо!

Исключение при возврате словаря
System.NotSupportedException: тип коллекции 'System.Collections.Generic.Dictionary`2 [System.Int32, System.String]' не поддерживается.
в System.Text.Json.JsonClassInfo.GetElementType (параметры Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.CreateProperty (тип объявленPropertyType, тип runtimePropertyType, PropertyInfo propertyInfo, тип parentClassType, параметры JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.AddProperty (параметры типа propertyType, PropertyInfo propertyInfo, типа classType, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.AddPolicyProperty (параметры типа propertyType, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo..ctor (тип типа, параметры JsonSerializerOptions)
в System.Text.Json.JsonSerializerOptions.GetOrAddClass (Тип classType)
в System.Text.Json.WriteStackFrame.Initialize (тип типа, параметры JsonSerializerOptions)
в System.Text.Json.JsonSerializer.WriteAsyncCore (поток utf8Json, значение объекта, тип типа, параметры JsonSerializerOptions, CancellationToken cancellationToken)
в Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (контекст OutputFormatterWriteContext, выбранное кодированиеEncoding)
в Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (контекст OutputFormatterWriteContext, выбранное кодированиеEncoding)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (вызывающий ResourceInvoker, результат IActionResult)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (контекст ResultExecutedContextSealed)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Конец трассировки стека из предыдущего места, где было создано исключение ---
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (вызывающий ResourceInvoker, задача lastTask, состояние следующее, область действия, состояние объекта, логическое значение isCompleted)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (вызывающий ResourceInvoker)
в Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (конечная точка конечной точки, задача requestTask, средство ведения журнала ILogger)
в Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (контекст HttpContext)
в Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (контекст HttpContext)
в Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (контекст HttpContext)
в Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
в Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
в Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (контекст HttpContext)

Исключение при возврате BadRequest
System.NotSupportedException: тип коллекции Microsoft.AspNetCore.Mvc.SerializableError не поддерживается.
в System.Text.Json.JsonClassInfo.GetElementType (параметры Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.CreateProperty (тип объявленPropertyType, тип runtimePropertyType, PropertyInfo propertyInfo, тип parentClassType, параметры JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.AddProperty (параметры типа propertyType, PropertyInfo propertyInfo, типа classType, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo.AddPolicyProperty (параметры типа propertyType, JsonSerializerOptions)
в System.Text.Json.JsonClassInfo..ctor (тип типа, параметры JsonSerializerOptions)
в System.Text.Json.JsonSerializerOptions.GetOrAddClass (Тип classType)
в System.Text.Json.WriteStackFrame.Initialize (тип типа, параметры JsonSerializerOptions)
в System.Text.Json.JsonSerializer.WriteAsyncCore (поток utf8Json, значение объекта, тип типа, параметры JsonSerializerOptions, CancellationToken cancellationToken)
в Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (контекст OutputFormatterWriteContext, выбранное кодированиеEncoding)
в Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync (контекст OutputFormatterWriteContext, выбранное кодированиеEncoding)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 21_0 (вызывающий ResourceInvoker, результат IActionResult)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 29_0 TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow (контекст ResultExecutedContextSealed)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters ()
--- Конец трассировки стека из предыдущего места, где было создано исключение ---
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited | 19_0 (вызывающий ResourceInvoker, задача lastTask, состояние следующее, область действия, состояние объекта, логическое значение isCompleted)
в Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged | 17_1 (вызывающий ResourceInvoker)
в Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask | 6_0 (конечная точка конечной точки, задача requestTask, средство ведения журнала ILogger)
в Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke (контекст HttpContext)
в Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke (контекст HttpContext)
в Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke (контекст HttpContext)
в Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke (HttpContext httpContext)
в Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke (HttpContext httpContext, ISwaggerProvider swaggerProvider)
в Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (контекст HttpContext)

area-System.Text.Json enhancement

Самый полезный комментарий

Для новичков временное решение - вернуться к Newtonsoft.Json .

  1. Добавьте ссылку на пакет в Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Добавьте .AddNewtonsoftJson() сразу после .AddControllers() / .AddMvc() или любой другой комбинации.

Все 51 Комментарий

Обе ошибки «неподдерживаемое исключение» являются ограничениями встроенного сериализатора и являются конструктивными (по крайней мере, для того, что поставляется в версии 3.0).

Когда будет добавлена ​​эта поддержка? Поскольку это некоторое время поддерживается в Json.net и других сериализаторах, я надеюсь, что это на радаре.
Я ценю тот факт, что могу снова отказаться от использования Json.net, поэтому большое вам за это спасибо!

На нашем радаре есть множество возможностей сериализатора для поддержки в vNext (так 5.0) и более поздних версиях, одна из которых - поддержка настраиваемого словаря.

Asp.NET Web Apis, при возврате словаря происходит сбой NotSupportedException

При сериализации сегодня поддерживается только Dictionary<string, TValue> (т.е. строковые ключи TKeys). В вашем словаре <int, string> который не поддерживается.
https://github.com/dotnet/corefx/blob/93d7aa1c1737b6da29d04b78557215e18eb786d4/src/System.Text.Json/tests/Serialization/DictionaryTests.cs#L385 -L390

@steveharter , @layomia - есть ли пока возможное обходное решение? Что нужно сделать, чтобы добавить поддержку словаря без строковых ключей в сериализаторе для 5.0?

System.NotSupportedException: тип коллекции Microsoft.AspNetCore.Mvc.SerializableError не поддерживается.

~ Добавление поддержки типа SerializableError не было моей задачей. @pranavkm , @rynowak - какой здесь контекст? Я не знаком с ModelStateDictionary , может ли это поддерживаться в самом mvc с помощью специального конвертера? ~

Изменить: Неважно, это уже исправлено.

System.NotSupportedException: тип коллекции Microsoft.AspNetCore.Mvc.SerializableError не поддерживается.

Это была известная проблема https://github.com/aspnet/AspNetCore/issues/11459, которая была недавно исправлена ​​(как часть предварительной версии 8): https://github.com/dotnet/corefx/pull/39001

Большое спасибо за быстрые ответы @ahsonkhan!

«Ограничение» того, что ключ является строкой, действительно имеет смысл, когда я думаю об этом. Теперь я вижу, что Json.net фактически генерирует json с ключом, являющимся строкой, при десериализации он просто вернет мне int. Было бы определенно неплохо иметь в будущем поддержку нестроковых ключей, но не ограничивать их показы.

Хорошо, рад слышать, что ошибка Mvc.SerializableError, которая не поддерживается, была исправлена. Есть идеи, есть ли надежда на дату выпуска Preview 8? Пытался поискать и что-то найти, но ничего об этом не увидел.

Как только выйдет предварительная версия 8, мы попробуем еще раз попробовать библиотеку сериализации json .net core 3, но пока нам нужно придерживаться Json.net.

@steveharter , @layomia - есть ли пока возможное обходное решение? Что нужно сделать, чтобы добавить поддержку словаря без строковых ключей в сериализаторе для 5.0?

>

@ahsonkhan @ willyt150 обходным JsonConverter<T> где T - это Dictionary<int, string> .
См. Https://github.com/dotnet/corefx/issues/36639#issue -429928740 для некоторых примеров.

Есть идеи, есть ли надежда на дату выпуска Preview 8?

Некоторое время спустя в этом месяце.

Подумайте об этом еще раз, убрав на данный момент все, что нужно, так как это может быть то, что мы не хотим поддерживать по умолчанию.

Спасибо, @layomia, я посмотрю на это.

Спасибо @ahsonkhan , с нетерпением жду исправления!

Из @namse (с https://github.com/dotnet/corefx/issues/40404):

Привет. Когда я пытаюсь сериализовать словарь с помощью целочисленного ключа, он выдает исключение System.NotSupportedException.

Я думаю, что имеет смысл поддерживать сериализацию Json, в которой Dictionary имеет ключ ToString -able. например, когда мы запускаем ToString для int или boolean, он возвращает «123» или «true». Я думаю, что это ключ ToString -able.

Verison

System.Text.Json Версия Nuget: 4.6.0-preview8.19405.3

Код

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

Ожидал

"{"5": "five"}"

Но что случилось

Выдана ошибка System.NotSupportedException

На самом деле, когда я меняю Newtonsoft.Json на System.Text.Json, возникает проблема совместимости. Они возвращают строку, как я и ожидал. Я думаю, что System.Text.Json не обязательно должен быть совместимым, но ... вы знаете.

Я реализовал конвертер, который поддерживает как сериализацию, так и десериализацию для IDictionary<TKey, TValue> где TKey имеет статический метод 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;
        }
    }
}

Тестовое задание:

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

Результат:

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

Однако он по-прежнему не может сериализовать вложенный словарь, такой как Dictionary<int, Dictionary<int, int>> потому что System.Text.Json не принимает внутренний тип Dictionary<int, int> . Думаю, это ошибка.

Однако он по-прежнему не может сериализовать вложенный словарь, такой как словарь.> потому что System.Text.Json не принимает словарь внутреннего типа. Думаю, это ошибка.

Поддержка только <string, x> предусмотрена намеренно из-за сокращения объема работ для выпуска версии 3.0. Выпуск 3.0 задуман как минимально жизнеспособный продукт с поддерживаемыми наиболее распространенными сценариями.

@steveharter По крайней мере, вы не должны бросать пригодный для использования конвертер.

Есть ли какие-либо планы по поддержке этого в .net core 3.1?

Для новичков временное решение - вернуться к Newtonsoft.Json .

  1. Добавьте ссылку на пакет в Microsoft.AspNetCore.Mvc.NewtonsoftJson .
  2. Добавьте .AddNewtonsoftJson() сразу после .AddControllers() / .AddMvc() или любой другой комбинации.

@steveharter По крайней мере, вы не должны бросать пригодный для использования конвертер.

Да, это справедливый вопрос. Возможно, мы сможем снять это ограничение для 3.1. cc @layomia

Также просто чтобы уточнить, что сегодня элементы словаря сериализуются как свойства, что возможно, потому что ключ является строкой. Поддержка нестроковых ключей означает, что элементы будут сериализованы как KeyValuePair.

О боже, эта проблема возникла сразу после того, как я обновился до 3.0.

Пришлось установить пакет newton с помощью AddNewtonsoftJson.

Из @israellot в https://github.com/dotnet/corefx/issues/41345

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

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

Эта простая сериализация хорошо обрабатывается бывшей стандартной библиотекой Newtonsoft json путем сериализации ключа int в виде строки. В System.Text.Json выдается неподдерживаемое исключение.

@israellot , @unruledboy и другие участники потока, можете ли вы предоставить подробную информацию о том, почему ваша объектная модель требует в ваших сценариях словарей с целочисленными ключами и почему изменение его значения на Dictionary<string, TValue> не сработает? Я хотел бы увидеть некоторые примеры использования для сбора требований и помочь мотивировать исправление.

В любом случае они будут сериализованы как строки, поэтому я не понимаю, в каких сценариях вы бы хотели, чтобы в вашем базовом словаре были ключи int32.

@ahsonkhan Я считаю, что главная мотивация - это совместимость.
Предыдущим сериализатором по умолчанию был Newtonsoft, поэтому пользователи могли писать целые модели, полагаясь на его способность сериализовать и десериализовать произвольные классы. Переход с 2.X на 3.0 теперь приведет к незаметному критическому изменению, поскольку мы узнаем о несовместимости только во время выполнения.

Я считаю, что многие сценарии включают использование json в качестве передачи по сети, и в этом случае модель предметной области может быть Dictionary. Ваше предложение сводится к созданию отдельного словаря объектов DTOи преобразование между ними кажется довольно неэффективным, поскольку теперь нам нужно выделить другой объект, чтобы он соответствовал сериализатору.
Если смотреть строго на сериализатор, ограничение словаря строковыми ключами логично, поскольку это единственное возможное представление json. Но, учитывая, что сериализатор играет важную роль в приложениях, я считаю, что лучше всего убрать как можно больше трений.

Я столкнулся с этой проблемой с небольшой программой, которую я пишу, где части метки версии предоставляются через файл json.
Части этикетки имеют ключ, указывающий индекс, в который может быть вставлена ​​часть этикетки. Это означает, что клавиши представляют собой числовые значения, например

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

Используя Newtonsoft, json можно без проблем десериализовать до Dictionary<int, string> . После преобразования в System.Text.Json сериализация не удалась.

Я решил эту проблему, создав свои собственные DictionaryConverter и DictionaryConverter. .
Я также создал простой конвертер, который позволяет десериализовать целые числа из строки

Затем они регистрируются с помощью параметров сериализатора: https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22

Эти изменения позволяют десериализовать ключи словаря вместо прямого чтения в виде строки. Это дополнительно открывает поддержку ключей произвольных типов, которые могут иметь свои собственные преобразователи, зарегистрированные для сериализации (например, перечисления / типы / типы, которые могут быть сериализованы как уникальные ключи и т. Д.)

Я официально не тестировал вещи, но в рамках текущей разработки, похоже, проблема решена.

Установка контрольной точки для версии 3.1 для снятия любых ограничений, мешающих созданию настраиваемого конвертера, который может обрабатывать любые TKey за Dictionary<TKey,TValue> .

Обновление: я добавил образцы, которые работают с 3.0. Я не заметил никаких проблем, таких как вложенные словари, о которых говорилось выше.

В примерах используются два формата JSON:

  • Объект JSON со строковыми свойствами: {"1":"val1","2":"val2"} даже если TKey не является строкой.

    • Они полагаются на метод TryParse соответствующего поддерживаемого типа ключа. Для v3.0 невозможно обеспечить поддержку обобщенного TKey поскольку методы TryParse могут быть разными для любых TKey а также из-за того, что необходимо указать параметры языка и региональных параметров. (обычно инвариантный). Таким образом, приведенные ниже примеры относятся к одному типу TKey .

    • Sample Dictionary<int, string> . Это простой пример всего для TValue == string .

    • Sample Dictionary<Guid, TValue> . Это может обрабатывать любые TValue .

    • Sample Dictionary<TKey, TValue> where TKey is Enum . Для перечислений; это может обрабатывать любые TValue .

  • Массив JSON с записями KeyValuePair: [{"Key":1,"Value":"val1"},{"Key":2,"Value":"val2"}]

    • Sample Dictionary<int, string> . Это простой пример всего для TValue == string .

    • Sample Dictionary<TKey, TValue> . Это может обрабатывать любые TKey и TValue потому что типы изначально представлены в JSON и не требуют TryParse .

Если эти образцы удовлетворительны, я изменю эту проблему на 5.0, чтобы обсудить, предоставляем ли мы встроенную поддержку, не требующую специальных преобразователей.

Установка контрольной точки на 5.0 для рассмотрения (что, если какой-либо из приведенных выше примеров должен работать по умолчанию).

Десериализация также, по-видимому, сопоставляет общие типы объектов с JsonDocument, а не с их обычными (примитивными?) Типами.

Пример:

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"] отображается как: ValueKind = Number: "86"
NewtonSoftJson [0] ["id"] отображается как: 86

@steveharter Словарь с ключом перечисления с этим конвертером сериализуется как:
{ "Stuff": [ { "Key": 1, "Value": "String" }, { "Key": 3, "Value": "String" }, { "Key": 2, "Value": "String" } ] }

Хотя JSON.NET дает то, что, как я предполагаю, ожидает большинство людей:
{ "Stuff": { "Item1": "String", "Item2": "String", "Item3": "String" }

Десериализация также, по-видимому, сопоставляет общие типы объектов с JsonDocument, а не с их обычными (примитивными?) Типами.

Да, это задумано. См. Https://github.com/dotnet/corefx/issues/38713

@steveharter Словарь с ключом перечисления с этим конвертером сериализуется как:
{"Stuff": [{"Key": 1, "Value": "String"}, {"Key": 3, "Value": "String"}, {"Key": 2, "Value": " Нить" } ] }
Хотя JSON.NET дает то, что, как я предполагаю, ожидает большинство людей:
{"Stuff": {"Item1": "String", "Item2": "String", "Item3": "String"}

Я предполагаю, что вы используете "Образец словаря""? Если да, то да, это использовалось KeyValuePair, у которого есть свойства Key" и "Value". Я не предоставил образец словарей перечисления на основе TKey, которые сериализуются как имена свойств, но я буду работать над его добавлением.

Ага, вот этот. А, ладно, я думал, вы имели в виду это как универсальный сериализатор словаря.
Интересно увидеть ваш новый образец, когда он будет доступен, поскольку тот, который мы сейчас используем, не кажется таким быстрым, как мне бы хотелось.

@roguecode вот пример Enum для Dictionary<TKey, TValue> где TKey является Enum и использует синтаксис JSON "свойства" вместо KeyValuePair. Я также обновил список образцов выше, включив в него этот новый образец.

Здравствуйте, у меня есть что-то похожее, но другое, и мне интересно, можете ли вы указать мне, где еще искать.

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

Запуск как 3.0:

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

Если у меня есть этот объект 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"

В 2.2 выполнение Newtonsoft.Json.JsonConvert.SerializeObject(problemDetails) возвращает

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

В 3.0 он возвращает:

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

Сериализованная строка версии 3.0 включает IDictionaryимя свойства, Extensions , и мы можем правильно десериализовать эту строку в версии 3.0. Вы можете видеть, что это имя свойства отсутствует в версии 2.x.

Проблема в том, что сериализация 3.0, которая происходит, когда ответ возвращается от фильтра с использованием BadRequestObjectResult , из приведенного ниже кода:

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

..., возвращаемое содержимое ответа имеет ту же форму, что и версия 2.2 (имя свойства Extensions исключено), что приводит к десериализации свойства Extensions как пустой коллекции (с использованием Newtonsoft.Json.JsonConvert.DeserializeObject<ValidationProblemDetails>() )

Почему-то эта сериализация не использует ту же сериализацию, что и библиотека Newtonsoft, с которой мы пытаемся десериализоваться.

Спасибо за внимание!

У меня есть что-то похожее, но другое

@ ts46235, не могли бы вы открыть для этого новый выпуск, поскольку он отличается от текущего?

@ ts46235 Я ответил на ваш вопрос в другом выпуске, который вы открыли здесь - https://github.com/aspnet/AspNetCore/issues/16618. Отметить здесь беседу как не по теме.

обновлен до Core 3.1 и до сих пор не исправлен

Я только что обновился до 3.1 и получил удар. Вернуться к JSON.NET Я иду ... (я использую ключи GUID)

Dot net ядро ​​3.1
Словарьтакже не работает, даже объект в ключе на самом деле является строкой

Я тоже только что нажал на это, и какое это пугающее тихое ограничение, поскольку, как указывали другие, вы не увидите этого во время компиляции. В моем случае я хочу сериализовать Dictionary<int, List<string>> , что не кажется мне особенно экзотическим.

Они должны исправить это, но я вижу это снова и снова, даже со старым форматтером, двоичным форматером раннего newtsoft, словарями в словарях, словарями с интерфейсами. Они должны исправить это, но если вы не хотите неприятностей, люди действительно не должны помещать сложные объекты, такие как словари, в контракты сериализации, которые вы просите о проблемах - newtsoft испортил людей. Посмотрите на все общедоступные свойства в Dictionary count и т. Д. Вы полагаетесь на что-то настраиваемое в сериализаторе, чтобы сопоставить это.

К сожалению, в C # нет простого типа для имен свойств, поэтому Dictionary принудительно. Так что мне просто грустно ...

Вот обходной путь, но отнюдь не полное решение:

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

Возможно, поддержка (некоторых) сериализаторов общих типов могла бы быть добавлена ​​OOTB, например, этот список , а также, если один из этих типов .IsAssignableFrom(systemDotObjectInstance.GetType()) для поддержки Dictionary<<ins i="7">object</ins>, V> .

Вот предложение по добавлению поддержки TKey типов
https://github.com/dotnet/runtime/pull/32676

Пожалуйста, дайте нам знать о любых мыслях или проблемах.

В частности, оставьте отзыв о наборе типов, которые @Jozkee планирует поддерживать для ключей словаря, особенно если вам нужна поддержка других типов (в основном, все встроенные примитивные числовые типы, перечисления и некоторые другие):
https://github.com/dotnet/runtime/blob/a5d96c0e280b56412d4614848f5ee3b1e0d7f216/src/libraries/System.Text.Json/docs/KeyConverter_spec.md#keyconverter

Словарьтакже не работает, даже объект в ключе на самом деле является строкой

@ andrew-vdb, поддерживающий сериализацию произвольных ключей объекта, скорее всего, останется неподдерживаемой. Однако, если тип среды выполнения ключей объекта является одним из «вновь поддерживаемых» типов, то сериализация будет работать для этого, как только функция будет выполнена. Однако десериализация останется в виде JsonElement в штучной упаковке (пока не будет решена эта проблема, связанная с выбором другого поведения): https://github.com/dotnet/runtime/issues/29960

@Jozkee, какие типы включены для TValue ? Предположительно все, что вы в настоящее время можете сериализовать как отдельный объект?

Предположительно все, что вы в настоящее время можете сериализовать как отдельный объект?

да.

Десериализация также, по-видимому, сопоставляет общие типы объектов с JsonDocument, а не с их обычными (примитивными?) Типами.

Пример:

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"] отображается как: ValueKind = Number: "86"
NewtonSoftJson [0] ["id"] отображается как: 86

Из всех перечисленных проблем это беспокоит меня больше всего. Списокили T [] или Словарьдолжен правильно десериализоваться для любого типа, который может быть сопоставлен непосредственно с несколькими типами, которые Json имеет, с типами CLR.

Выпуск 3.0 задуман как минимально жизнеспособный продукт с поддерживаемыми наиболее распространенными сценариями.

Интересно, как Список> не один из самых распространенных сценариев:

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

Поскольку это также десериализуется в System.Text.JsonElement, где я ожидал бы double (Number) и string (String)

Я столкнулся с этой проблемой с небольшой программой, которую я пишу, где части метки версии предоставляются через файл json.
Части этикетки имеют ключ, указывающий индекс, в который может быть вставлена ​​часть этикетки. Это означает, что клавиши представляют собой числовые значения, например

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

Используя Newtonsoft, json можно без проблем десериализовать до Dictionary<int, string> . После преобразования в System.Text.Json сериализация не удалась.

Я решил эту проблему, создав свои собственные DictionaryConverter и DictionaryConverter. .
Я также создал простой конвертер, который позволяет десериализовать целые числа из строки

Затем они регистрируются с помощью параметров сериализатора: https://github.com/Kieranties/SimpleVersion/blob/feature/netcore3/src/SimpleVersion.Core/Serialization/Serializer.cs#L20

Эти изменения позволяют десериализовать ключи словаря вместо прямого чтения в виде строки. Это дополнительно открывает поддержку ключей произвольных типов, которые могут иметь свои собственные преобразователи, зарегистрированные для сериализации (например, перечисления / типы / типы, которые могут быть сериализованы как уникальные ключи и т. Д.)

Я официально не тестировал вещи, но в рамках текущей разработки, похоже, проблема решена.

привет @Kieranties , github ссылается на 404 для меня

привет @Kieranties , github ссылается на 404 для меня

@AirEssY Я исправил ссылки в моем исходном комментарии, думаю, теперь они находятся в мастерской по адресу: https://github.com/Kieranties/SimpleVersion/tree/master/src/SimpleVersion.Core/Serialization

Если словарь эквивалентен карте JavaScript, тогда любой (тип JS, представленный в C #) должен быть приемлемым,

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

Пример стандартного подхода к десериализации карты в JS:

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

console.log(countries)

Что производит:

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

TL; DR: ограничение ключей строками плохо работает с JS

@Jozkee, так это будет только в .NET 5 или будет в 3. *?

@onionhammer .NET 5.0, вы также можете опробовать эту функцию в следующей предварительной версии (5.0 preview8).
Нет планов переносить это на 3.x.

Решение для 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 , это не желаемый результат

@Jozkee, так это будет только в .NET 5 или будет в 3. *?

Это не будет перенесено на 3.x, но вы можете добавить использование пакета NuGet System.Text.Json в свой проект, чтобы получить все новые функции в .NET 5.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги