Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonSerializer throws InvalidCastException #31553

Closed
folkcoder opened this issue Nov 23, 2019 · 9 comments · Fixed by #33819
Closed

JsonSerializer throws InvalidCastException #31553

folkcoder opened this issue Nov 23, 2019 · 9 comments · Fixed by #33819
Labels
area-System.Text.Json bug help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@folkcoder
Copy link

Using .NET Core 3.0 or 3.1.100-preview3-014645, an ICollection<string> property with a private backing field assigned a custom implementation fails to serialize.

    public class Client
    {
        private ICollection<string> _allowedGrantTypes = new GrantTypeValidatingHashSet();

        // this works 
        // public GrantTypeValidatingHashSet AllowedGrantTypes { get; set; }

        // this works 
        // public ICollection<string> AllowedGrantTypes { get; set; } = new GrantTypeValidatingHashSet();

        // this errors
        public ICollection<string> AllowedGrantTypes
        {
            get { return _allowedGrantTypes; }
            set
            {
                _allowedGrantTypes = new GrantTypeValidatingHashSet();
            }
        }
    }

Error message

System.InvalidCastException: 'Unable to cast object of type 'SerializationSample.GrantTypeValidatingHashSet' to type 'System.Collections.Generic.IList`1[System.String]'.'

Stack trace

at System.Text.Json.JsonSerializer.ApplyValueToEnumerable[TProperty](TProperty& value, ReadStack& state)
   at System.Text.Json.JsonPropertyInfoNotNullable`4.OnReadEnumerable(ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonPropertyInfo.ReadEnumerable(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at SerializationSample.Program.Main() in C:\SerializationSample\SerializationSample\Program.cs:line 13

This was discovered while attempting to serialize a Client model from IdentityServer4. I've created a bare-bones project duplicating the error. Serialization occurs correctly with Newtonsoft.Json.

@layomia layomia self-assigned this Nov 23, 2019
@layomia layomia removed their assignment Dec 3, 2019
@Fr1z2r
Copy link

Fr1z2r commented Jan 30, 2020

Here is another simpler example. This code throws an exception when IgnoreNullValues=true, and doesn't throw when IgnoreNullValues=false:

class MyClass
{
    public HashSet<int> X { get; set; } = new HashSet<int>();
}

public static void Main()
{
    JsonSerializer.Deserialize<MyClass>(@"{""X"":[2,1]}", 
        new JsonSerializerOptions {IgnoreNullValues = true});
}
System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.HashSet`1[System.Int32]' to type 'System.Collections.IList'.
   at System.Text.Json.JsonSerializer.ApplyObjectToEnumerable(Object value, ReadStack& state, Boolean setPropertyDirectly)
   at System.Text.Json.JsonSerializer.HandleEndArray(JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@gingters
Copy link

This should also be fixed in 3.0 (still in Maintenance) and 3.1 (LTS). I reproduced that issue also in 3.1.101 and in 3.1.200-preview-014883.

@GioviQ
Copy link

GioviQ commented Mar 27, 2020

@benedict1986
Copy link

I am using 3.1.0 and also having this issue.

@GioviQ
Copy link

GioviQ commented Mar 29, 2020

In fact System.Text.Json.JsonSerializer does not support HashSet<>

@am11
Copy link
Member

am11 commented Mar 29, 2020

Seems like it has been fixed in the master branch for .NET 5 - https://github.com/dotnet/installer/blob/master/README.md#installers-and-binaries:

#!/usr/bin/env bash

# linux

mkdir /tmp/dotnet5
curl -sSL https://aka.ms/dotnet/net5/dev/Sdk/dotnet-sdk-linux-x64.tar.gz | tar -xvz -C /tmp/dotnet5 > /dev/null

/tmp/dotnet5/dotnet new console -n testhashset

cat <<'EOF' > testhashset/Program.cs
using System;
using System.Collections.Generic;
using System.Text.Json;

class Program
{
    class MyClass
    {
        public HashSet<int> X { get; set; } = new HashSet<int>();
    }

    public static void Main()
    {
        var myObject = JsonSerializer.Deserialize<MyClass>(@"{""X"":[2,1]}",
            new JsonSerializerOptions { IgnoreNullValues = true });

        Console.WriteLine("Count of {0} is: {1}", nameof(myObject.X), myObject.X.Count);
    }
}
EOF

/tmp/dotnet5/dotnet publish testhashset/ -o my-publish-dir/ /p:PublishSingleFile=true -r linux-x64

./my-publish-dir/testhashset

# prints
#> Count of X is: 2

@benedict1986
Copy link

Does it mean I won't be able to use it in my Asp.Net Core 3.1 project? That sounds terrible since .net core 5.0 is in preview and some other dependencies may have some issue.

@GioviQ
Copy link

GioviQ commented Mar 29, 2020

Until then

public static class HttpClientJsonExtensions
    {
        //System.Text.Json does not like HashSet. So I have to use Newtonsoft
        //See https://github.com/dotnet/runtime/issues/31553
        //TODO remove and use GetFromJsonAsync in the future
        public static async Task<T> GetNewtonsoftJsonAsync<T>(this HttpClient httpClient, string requestUri)
        {
            var stringContent = await httpClient.GetStringAsync(requestUri);
            return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(stringContent);
        }
    }

@benedict1986
Copy link

Thank you @GioviQ for your tip. I eventually make it working by installing Microsoft.AspNetCore.Mvc.NewtonsoftJson and then use .AddNewtonsoftJson() in the following location:

services
.AddControllersWithViews(option => option.EnableEndpointRouting = false) 
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddNewtonsoftJson();

@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json bug help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants