Runtime: 新的 Asp.NET Core 3.0 Json 不会序列化字典<key/>

创建于 2019-08-07  ·  51评论  ·  资料来源: dotnet/runtime

.NET Core 3.0 预览版 7

Asp.NET Web Apis,当返回一个 Dictionary 时它会失败并返回 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 options)
在 System.Text.Json.JsonClassInfo.CreateProperty(类型声明的属性类型,类型运行时属性类型,属性信息属性信息,类型 parentClassType,JsonSerializerOptions 选项)
在 System.Text.Json.JsonClassInfo.AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
在 System.Text.Json.JsonClassInfo.AddPolicyProperty(类型 propertyType,JsonSerializerOptions 选项)
在 System.Text.Json.JsonClassInfo..ctor(类型类型,JsonSerializerOptions 选项)
在 System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType)
在 System.Text.Json.WriteStackFrame.Initialize(类型类型,JsonSerializerOptions 选项)
在 System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json,对象值,类型类型,JsonSerializerOptions 选项,CancellationToken 取消令牌)
在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
在 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 context)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter,TFilterAsync
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束 ---
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker。g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker。g__Logged|17_1(ResourceInvoker 调用者)
在 Microsoft.AspNetCore.Routing.EndpointMiddleware。g__AwaitRequestTask|6_0(端点端点,任务请求任务,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 options)
在 System.Text.Json.JsonClassInfo.CreateProperty(类型声明的属性类型,类型运行时属性类型,属性信息属性信息,类型 parentClassType,JsonSerializerOptions 选项)
在 System.Text.Json.JsonClassInfo.AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
在 System.Text.Json.JsonClassInfo.AddPolicyProperty(类型 propertyType,JsonSerializerOptions 选项)
在 System.Text.Json.JsonClassInfo..ctor(类型类型,JsonSerializerOptions 选项)
在 System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType)
在 System.Text.Json.WriteStackFrame.Initialize(类型类型,JsonSerializerOptions 选项)
在 System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json,对象值,类型类型,JsonSerializerOptions 选项,CancellationToken 取消令牌)
在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
在 Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
在 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 context)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext TFilter,TFilterAsync
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束 ---
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker。g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker。g__Logged|17_1(ResourceInvoker 调用者)
在 Microsoft.AspNetCore.Routing.EndpointMiddleware。g__AwaitRequestTask|6_0(端点端点,任务请求任务,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. .AddControllers() / .AddMvc()或任何其他组合之后添加.AddNewtonsoftJson()

所有51条评论

“不支持的异常”错误都是内置序列化程序中的限制,并且是设计使然(至少对于 3.0 中的内容)。

何时添加此支持? 由于 Json.net 和其他序列化程序已经支持了一段时间,我希望这能引起注意。
我非常感谢我可以选择重新使用 Json.net,因此非常感谢您!

在 vNext(所以 5.0)及更高版本中,我们的雷达支持一系列序列化器功能,自定义字典支持就是其中之一。

Asp.NET Web Apis,当返回一个字典时它失败并返回 NotSupportedException

序列化时,目前仅支持Dictionary<string, TValue> (即字符串类型的 TKey)。 您的字典是不支持的<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 :

非常感谢您的快速回复@ahsonkhan!

当我想到它时,键是字符串的“限制”实际上是有道理的。 我现在看到 Json.net 实际上生成了 json,键是一个字符串,反序列化它只会让我返回一个 int。 将来支持非字符串键肯定会很好,但不会阻止。

好的,很高兴听到不支持的 Mvc.SerializableError 已得到修复。 关于预览版 8 的发布日期是否有任何想法? 试图搜索并找到一些东西,但没有看到任何关于它的东西。

预览 8 发布后,我们将再次尝试使用 .net core 3 json 序列化库,但现在我们需要坚持使用 Json.net

@steveharter@layomia - 同时这里有潜在的解决方法吗? 在 5.0 的序列化程序中添加对非字符串键字典的支持需要什么?

>

@ahsonkhan @willyt150解决方法是使用实​​现JsonConverter<T>的自定义转换器,其中TDictionary<int, string>
有关一些示例,请参阅https://github.com/dotnet/corefx/issues/36639#issue -429928740。

关于预览版 8 的发布日期是否有任何想法?

本月晚些时候。

再考虑一下,暂时取消抢购,因为它可能是我们默认不希望支持的。

谢谢@layomia,我会研究一下。

谢谢@ahsonkhan ,期待修复!

来自@namse (来自 https://github.com/dotnet/corefx/issues/40404):

你好。 当我尝试使用整数键序列化 Dictionary 时,它会抛出 System.NotSupportedException。

我认为支持 Json 序列化是有意义的,其中 Dictionary 具有ToString -able 键。 例如,当我们为 int 或 boolean 运行 ToString 时,它返回“123”或“true”。 我认为该密钥是ToString -able 密钥。

版本

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 不接受内部类型 Dictionary. 我认为这是一个错误。

仅支持<string, x>是设计使然,这是为了针对 v3.0 进行范围切割。 3.0 版本旨在成为支持最常见场景的最小可行产品。

@steveharter至少当有可用的转换器时,你不应该抛出 notsupportexception。

.net core 3.1 中是否有支持此功能的计划?

对于新人,临时的解决方案是恢复到Newtonsoft.Json

  1. 添加对Microsoft.AspNetCore.Mvc.NewtonsoftJson包引用。
  2. .AddControllers() / .AddMvc()或任何其他组合之后添加.AddNewtonsoftJson()

@steveharter至少当有可用的转换器时,你不应该抛出 notsupportexception。

是的,这是一个公平的观点。 也许我们可以取消 3.1 的这个限制。 抄送@layomia

也只是为了澄清今天字典元素像属性一样被序列化,这是可能的,因为键是一个字符串。 支持非字符串键意味着元素将被序列化为 KeyValuePair。

哦,天哪,我升级到 3.0 后就遇到了这个问题。

必须使用 AddNewtonsoftJson 安装 newton 包。

来自@israellothttps://github.com/dotnet/corefx/issues/41345

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

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

通过将 int 键序列化为字符串,以前的默认 Newtonsoft json 库可以很好地处理这种简单的序列化。 在 System.Text.Json 上引发不支持的异常。

@israellot@unruledboy和线程上的其他人,您能否详细说明为什么您的对象模型在您的场景中需要带有整数键的 Dictionaries 以及为什么将其更改为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 后序列化失败。

我通过创建自己的DictionaryConverterDictionaryConverter解决了这个问题 .
我还创建了一个简单的转换器,允许从字符串反序列化整数

然后通过序列化程序选项注册这些:https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22

这些更改允许反序列化字典的键,而不是直接作为字符串读取。 这进一步开放了对密钥为任意类型的支持,这些类型可以注册自己的转换器以进行序列化(例如,可以序列化为唯一键的枚举/类型/类型等)

我还没有正式测试过东西,但在当前的开发中,这似乎已经解决了这个问题。

为 3.1 设置里程碑以删除阻止创建自定义转换器的任何限制,该转换器可以处理Dictionary<TKey,TValue>任何TKey Dictionary<TKey,TValue>

更新:我添加了适用于 3.0 的示例。 我没有注意到任何问题,例如上面报告的嵌套字典。

示例中使用了两种 JSON 格式:

  • 具有字符串属性的 JSON 对象: {"1":"val1","2":"val2"}即使TKey不是字符串。
  • 带有 KeyValuePair 条目的 JSON 数组: [{"Key":1,"Value":"val1"},{"Key":2,"Value":"val2"}]

如果这些示例令人满意,我会将这个问题更改为 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 给出了我认为大多数人会期望的内容:
{ "东西": { "Item1": "String", "Item2": "String", "Item3": "String" }

我假设您正在使用“示例词典“?如果是这样,是的,使用具有“键”和“值”属性的 KeyValuePair。我没有提供序列化为属性名称的基于 TKey 的枚举字典的示例,但我将努力添加它。

是的,那个。 嗯,好吧,我以为您是将其作为通用字典序列化程序的意思。
有兴趣在可用时查看您的新样本,因为我们目前使用的样本似乎没有我希望的那么快。

@roguecode这里是Dictionary<TKey, TValue>的枚举示例,其中 TKey 是枚举并使用“属性”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 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 版本中省略了此属性名称。

问题是当响应从使用BadRequestObjectResult的过滤器返回时发生的 3.0 序列化,来自下面的代码:

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

点网核心3.1
字典也不起作用,即使键中的对象实际上是字符串

我也刚碰到这个,这是一个多么可怕的无声限制,因为正如其他人指出的那样,您在编译时不会看到它。 就我而言,我想序列化Dictionary<int, List<string>> ,这并没有让我觉得特别异国情调。

他们应该修复它,但我一次又一次地看到这种情况,即使使用旧的格式化程序、早期的 newtsoft 二进制格式化程序、字典中的字典、带接口的字典。 他们应该修复它,但如果你不想麻烦,人们真的不应该把像 Dictionaries 这样的复杂对象放在序列化合同中,你会自找麻烦——newtsoft 已经宠坏了人们。 查看字典计数等的所有公共属性,您依赖序列化程序中的自定义内容来映射它。

不幸的是,在 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
        };
    }

也许可以添加对(某些)常见类型序列化程序的支持,例如这个 list ,并且如果这些类型之一.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 :

@JozkeeTValue启用的类型是什么? 大概您当前可以序列化为独立对象的任何内容?

大概您当前可以序列化为独立对象的任何内容?

是的。

反序列化似乎也将通用对象类型映射到 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,我希望在其中使用双精度(数字)和字符串(字符串)

我在编写的一个小程序中遇到了这个问题,其中部分版本标签是通过 json 文件提供的。
标签部分有一个键,用于指定可以插入标签部分的索引。 这意味着键是数值,例如

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

使用 Newtonsoft,可以将 json 反序列化而不会出现Dictionary<int, string> 。 转换为 System.Text.Json 后序列化失败。

我通过创建自己的DictionaryConverterDictionaryConverter解决了这个问题 .
我还创建了一个简单的转换器,允许从字符串反序列化整数

然后通过序列化程序选项注册这些: https :

这些更改允许反序列化字典的键,而不是直接作为字符串读取。 这进一步开放了对密钥为任意类型的支持,这些类型可以注册自己的转换器以进行序列化(例如,可以序列化为唯一键的枚举/类型/类型等)

我还没有正式测试过东西,但在当前的开发中,这似乎已经解决了这个问题。

@Kieranties ,github 链接 404 给我

@Kieranties ,github 链接 404 给我

@AirEssY我已经更正了原始评论中的链接,我认为现在掌握在: https :

如果 Dictionary 相当于一个 JavaScript Map,那么任何(用 C# 表示的 JS 类型)都应该是可以接受的,

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 中反序列化 Map 的标准方法的一个例子是:

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 预览版8)中试用该功能。
没有将其移植到 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,但您可以在项目中添加使用System.Text.Json NuGet 包以获取 .NET 5 中的所有新功能。

此页面是否有帮助?
0 / 5 - 0 等级