runtime: New Asp.NET Core 3.0 Json doesn't serialize Dictionary
.NET Core 3.0 Preview 7
Asp.NET Web Apis, when returning a Dictionary it fails with a NotSupportedException. I’ve included the exception below.
Also, the ControllerBase.BadRequest
method takes in a ModelStateDictionary
, but when that’s returned the serializer blows up as well with a NotSupportedException, but a slightly different message.
When will this support be added? Since this has been supported in Json.net and other serializers for a while I hope this is on the radar.
I do appreciate the fact that I can opt back in to using Json.net, so thank you very much for that!
Exception when returning a Dictionary System.NotSupportedException: The collection type ‘System.Collections.Generic.Dictionary`2[System.Int32,System.String]’ is not supported. at System.Text.Json.JsonClassInfo.GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.CreateProperty(Type declaredPropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.AddPolicyProperty(Type propertyType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo…ctor(Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType) at System.Text.Json.WriteStackFrame.Initialize(Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type type, JsonSerializerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|21_0(ResourceInvoker invoker, IActionResult result) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() — End of stack trace from previous location where exception was thrown — at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Exception when returning BadRequest System.NotSupportedException: The collection type ‘Microsoft.AspNetCore.Mvc.SerializableError’ is not supported. at System.Text.Json.JsonClassInfo.GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.CreateProperty(Type declaredPropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo.AddPolicyProperty(Type propertyType, JsonSerializerOptions options) at System.Text.Json.JsonClassInfo…ctor(Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type classType) at System.Text.Json.WriteStackFrame.Initialize(Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type type, JsonSerializerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|21_0(ResourceInvoker invoker, IActionResult result) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() — End of stack trace from previous location where exception was thrown — at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 57
- Comments: 51 (25 by maintainers)
Commits related to this issue
- long keys are bad (https://github.com/dotnet/runtime/issues/30524) — committed to enowars/EnoEngine by Trolldemorted 4 years ago
- [wip] Enodatabase (#41) * wip * Fix enocore.models.database namespace * Honor EnoELK logging definitions * long keys are bad (https://github.com/dotnet/runtime/issues/30524) * More longs ... — committed to enowars/EnoEngine by Trolldemorted 4 years ago
- Rolls back to Newtonsoft.Json because System.Text.Json dictionary support is not on a professional level https://github.com/dotnet/runtime/issues/30524 — committed to Elders/Pandora by mynkow 4 years ago
For the newcomers, the temporary solution is to revert back to
Newtonsoft.Json
.Microsoft.AspNetCore.Mvc.NewtonsoftJson
..AddNewtonsoftJson()
just after.AddControllers()
/.AddMvc()
or any other combination.Are there any plans to support this in .net core 3.1?
Setting milestone for 3.1 to remove any restrictions that prevents a custom converter from being created that can handle any
TKey
forDictionary<TKey,TValue>
.Setting milestone to 5.0 for consideration (what if any of the above examples should work by default).
I too just hit this, and what a scary silent limitation it is, since as others have pointed out you won’t see this at compile time. In my case, I want to serialize
Dictionary<int, List<string>>
, which doesn’t strike me as particularly exotic.@ahsonkhan I believe the key motivation is compatibility. The previous default serializer was Newtonsoft, so users may have written entire models relying on its ability to serialize and deserialize arbitrary classes. Migrating from 2.X to 3.0 will now cause a silent breaking change since we’ll only know the incompatibility at runtime.
I believe many scenarios involve using json just as transport across the wire, and in this case, the domain model might be Dictionary<int,TValue>. Your suggestion boils down to creating a separate DTO object Dictionary<string,TValue> and converting between the two, seems rather inefficient since now we need to allocate another object just to be compliant with the serializer. Looking strictly to the serializer, restricting dictionary to having string keys is logical as that’s the only possible json representation. But considering that the serializer plays a role in applications I believe it’s best to remove as much friction as possible.
@onionhammer .NET 5.0, you can also try out the feature in the next preview (5.0 preview8). There are no plans of porting this into 3.x.
I have implemented a converter which support both Serialization and Deserialization for
IDictionary<TKey, TValue>
whereTKey
has a static methodTKey Parse(string)
:Test:
Result:
However it still cannot serialize a nested Dictionary such as
Dictionary<int, Dictionary<int, int>>
because System.Text.Json doesn’t accept the inner typeDictionary<int, int>
. I think it’s a bug.Yes that is by design. See https://github.com/dotnet/corefx/issues/38713
Here’s the proposal for adding support to non-string
TKey
types in dictionaries. https://github.com/dotnet/runtime/pull/32676Please let us know any thoughts or concerns.
I just upgraded to 3.1 and got hit with this. Back to JSON.NET I go… (I use GUID keys)
Deserialization also seems to map generic object types to JsonDocument rather than their normal (primitive?) types.
Example:
SystemTextJson[0][“id”] shows as: ValueKind = Number : “86” NewtonSoftJson[0][“id”] shows as: 86
They should fix it but I see this time and time again even with the old formatter , binary formatter early newtsoft , dictionaries in dictionaries , dictionaries with interfaces . They should fix it but if you dont want trouble people really shouldn’t put complex objects like Dictionaries in serialization contracts your asking for trouble - newtsoft has spoiled people . Look at all the public properties on Dictionary count etc you are relying on something custom in the serializer to map this.
Unfortunate there is not a simple type for this in C# for property names so Dictionary is forced. So im just sad…
Update: I’ve added samples that work with 3.0. I didn’t notice any issues such as with nested dictionaries as reported above.
There are two JSON formats being used in the samples:
{"1":"val1","2":"val2"}
even thoughTKey
is not a string.TryParse
method on the corresponding supported key type. For v3.0 it is not feasible to provide support for a generalizedTKey
sinceTryParse
methods can be different for anyTKey
and because there are culture settings that need to be provided (typically Invariant). So the samples below are for a singleTKey
type.Sample Dictionary<int, string>
. This is a simple example for justTValue
==string
.Sample Dictionary<Guid, TValue>
. This can handle anyTValue
.Sample Dictionary<TKey, TValue> where TKey is Enum
. For Enums; this can handle anyTValue
.[{"Key":1,"Value":"val1"},{"Key":2,"Value":"val2"}]
Sample Dictionary<int, string>
. This is a simple example for justTValue
==string
.Sample Dictionary<TKey, TValue>
. This can handle anyTKey
andTValue
because the types are natively represented in JSON and don’t requireTryParse
.If these samples are satisfactory, I will change this issue to 5.0 in order to discuss whether we provide built-in support that don’t require custom converters.
This won’t be backported to 3.x but you can add use the System.Text.Json NuGet package in your project to get all the new features in .NET 5.
Solution for asp net core 3.x:
Here’s a workaround but by no means a complete solution:
@israellot, @unruledboy, and others on the thread, can you provide details on why your object model requires Dictionaries with integer keys in your scenarios and why changing it to be
Dictionary<string, TValue>
wouldn’t work? I would love to see some usages for gathering requirements and to help motivate the fix.They would get serialized as strings anyway, so I don’t understand in what scenarios you’d want to your underlying dictionary to have int32 keys instead.
Of all the mentioned issues, this bothers me the most. List<T> or T[] or Dictionary<string,object> should deserialize properly for any type that can be mapped directly from the few types Json has to CLR types.
I wonder how List<Dictionary<string,object>> isn’t one of the most common scenarios:
As this does also deserialize into System.Text.JsonElement, where I would expect double (Number) and string (String)
Some time later this month.
Thinking about this some more, removing the up-for-grabs for now since it may be something we don’t want to support by default.
@ahsonkhan @willyt150 the workaround for this is to use a custom converter that implements
JsonConverter<T>
whereT
isDictionary<int, string>
. See https://github.com/dotnet/corefx/issues/36639#issue-429928740 for some examples.Yes.
@roguecode here’s a Enum sample for
Dictionary<TKey, TValue>
where TKey is an Enum and uses the “property” JSON syntax instead of KeyValuePair. I also updated the list of samples above to include this new sample.I’ve ran into this issue with a small program I’m writing where parts of a version label are provided through a json file. The label parts have a key that specifies the index where the label part may be inserted. This means the keys are numeric values, e.g
Using Newtonsoft, the json can be deserialized without issue to a
Dictionary<int, string>
. After converting to System.Text.Json serialization failed.I’ve resolved this issue by creating my own DictionaryConverter, and DictionaryConverter<T,K>. I also created a simple converter that allows integers to be deserialized from a string
These are then registered through the serializer options: https://github.com/Kieranties/SimpleVersion/blob/master/src/SimpleVersion.Core/Serialization/Serializer.cs#L22
These changes allow the keys of a dictionary to be deserialized instead of directly read as a string. This further opens up support for keys to be arbitrary types that could have their own converters registered for serialization (e.g. enums/type/ types that may be serialized as unique keys etc)
I’ve not formally tested things but within the current development this seems to have resolved the issue.
From @israellot in https://github.com/dotnet/corefx/issues/41345
Oh boy, had this problem right after I upgraded to 3.0.
Had to install the newton package with AddNewtonsoftJson.
Yes that is a fair point. Perhaps we can remove this restriction for 3.1. cc @layomia
Also just to clarify that today dictionary elements are serialized like properties which is possible because the key is a string. Supporting non-string keys means elements will be serialized as a KeyValuePair.
Thank you very much for your quick replies @ahsonkhan!
The “limitation” of the key being a string actually makes sense when I think about it. I see now that Json.net actually generates json with the key being a string, when deserializing it would just get me an int back. It would definitely be nice having the support for non-string keys in the future, but not a show stopper.
Ok, glad to hear that the Mvc.SerializableError not being supported has been fixed. Any ideas on if there’s a hoped for release date of Preview 8? Tried to search and find something, but not seeing anything about that.
Once preview 8 comes out we’ll try giving the .net core 3 json serialization library a try again, but for now we’re needing to stick with Json.net
This was a known issue https://github.com/aspnet/AspNetCore/issues/11459 that was recently fixed (as part of preview 8): https://github.com/dotnet/corefx/pull/39001