-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
System.Text.Json cannot deserialize to IReadOnlySet #91875
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis Issue DetailsDescriptionReading through https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-types Strangely, When trying to deserialize to an Reproduction Steps
Expected behavior
I would also expect Actual behavior
Regression?Don't know. Known WorkaroundsUse Configuration
Windows 11 Pro x64
Other informationNo response
|
Because contract customization has not been fully implemented for collection types, it is not possible to work around this issue by customizing the We should audit all built-in collection interfaces and check whether they are supported by the serializer. One possibility is to honor the newly added |
Since |
Thanks for the update. @eiriktsarpalis do you see there being value in reconciling the documentation with current behaviour? |
Yes, I think we should be documenting what types are supported. |
Other read-only collection types e.g. |
There is no concrete type |
Don't |
They do if you are willing to accept worst performance of those collections in scenarios where they are just used in DTOs (no expected mutations). And if someone doesn't care about performance he may just stick with Newtonsoft.Json. |
If that is a concern, then |
Mutable events are not acceptable in event-driven architecture. |
How about using mutable DTOs that are separate from your domain events? Alternatively you could try implementing a custom read-only set that wraps a regular set. |
Thanks for the advice but, both options don't make sense in our use case. We will probably try to implement our own |
Hi @eiriktsarpalis, I have come up with custom converter for internal class ReadOnlySetConverterFactory : JsonConverterFactory
{
private readonly Type readOnlySetType = typeof(IReadOnlySet<>);
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == readOnlySetType;
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var genericTypeParameter = typeToConvert.GetGenericArguments()[0];
var hashSetConverter = options.GetConverter(typeof(HashSet<>).MakeGenericType(genericTypeParameter));
var converter = (JsonConverter)Activator.CreateInstance(
type: typeof(ReadOnlySetConverter<>).MakeGenericType(genericTypeParameter), BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { hashSetConverter },
culture: null)!;
return converter;
}
private class ReadOnlySetConverter<TValue> : JsonConverter<IReadOnlySet<TValue>>
{
private readonly JsonConverter<HashSet<TValue>> hashSetConverter;
private readonly Type hashSetType = typeof(HashSet<TValue>);
public ReadOnlySetConverter(JsonConverter<HashSet<TValue>> hashSetConverter) => this.hashSetConverter = hashSetConverter;
public override IReadOnlySet<TValue>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
hashSetConverter.Read(ref reader, hashSetType, options);
public override void Write(Utf8JsonWriter writer, IReadOnlySet<TValue> value, JsonSerializerOptions options) =>
hashSetConverter.Write(writer, value.ToHashSet(), options);
}
} |
Superficially, this looks good to me. The only caveat is that it wouldn't work in Native AOT due to use of unsupported reflection. |
@eiriktsarpalis After reading the discussion above, I still can't answer this question: why the framework couldn't use Is it just to prevent clients from breaking DTO's contract like this:
? Ok. Then, the solution is: don't break the declared DTO contract. But this proposal would perfectly fix the situation for 'legit' clients which doesn't brake the declared immutable contract and which don't try to cast a property of an immutable type to its mutable counterpart, wouldn't it? |
A value's actual runtime type is fair game for use. |
@CyrusNajmabadi Ok, If somebody has the practice of using a runtime type instead of a declared one in a class's API, what's an issue with the proposal even in this case? If somebody wants it - let him do it. But at the same time let the others ('legit' clients as I referred them above) declare Or one of the |
Choosing a type that is ok for the consumer to mutate when the producer does not want that. |
If it's something improper, then:
For what purpose does Is it a wrong declaration which should be removed? |
HashSet can serve as a read-only set when the producer of the set can decide they are ok with it being mutated. It is not appropriate when the producer does not want that and that possibility is forced on them. |
@CyrusNajmabadi I quite agree with @AbakumovAlexandr on this one. The ship has already sailed for the argument you are making via the JsonConverter treatment of existing readonly collections. @CyrusNajmabadi I would like to hear what solution you would propose for this issue. Hopefully you agree that the current state is awkward and it could be better? |
I would have it deserialize into a |
@CyrusNajmabadi yes that recent PR introducing ReadOnlySet seems like the perfect solve for this I would think. Is anyone in a position to put a code change together? I can give it a go at some point soon but have not contributed to this repo before. |
Keep in mind that |
If HashSet is picked to meet dotnet 8 timelines presumably it can't be changed for dotnet 9 and beyond to what would be the natural/obvious choice once ReadOnlySet ships due to risk of breaking code along the lines @CyrusNajmabadi has mentioned? That sounds a little awkward |
From my perspective using mutable implementations is not a huge issue from the perspective of deserialization. The deserializer produces the type and passes ownership off to the caller once completed. If the caller wants to take the risk of downcasting to the runtime type to make mutations it's on them. |
I don't think .NET Framework \ .NET Core ever guaranteed compatibility across versions when a runtime type is used instead of a declared contract type. To my knowledge, it always was solely on a client code risk. Honestly, I'd be surprised if any class library project would pick covering such the practice as its design goal. It always looks like a direct invasion into internal undocumented framework implementation details and shouldn't be guaranteed, at least from a maintainability perspective. |
Moving 10.0.0. This should be addressed holisitically including extending support for all collection types, adding support for CollectionBuilderAttribute and IEnumerable constructors (#80688) |
Description
Reading through https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-types
JsonSerializer.Deserialize
will support deserialization to a ton of different collection interfaces.Strangely,
IReadOnlySet
is not mentioned at all in the page I linked. Not even to say it's not supported, as some collection types have e.g.LinkedListNode<T>
.When trying to deserialize to an
IReadOnlySet
,JsonSerializer
throws aNotSupportedException
Reproduction Steps
dotnet new console
Program.cs
Expected behavior
JsonSerializer.Deserialize<IReadOnlySet<int>>("[1]");
ought to work, like it does forISet
or other readonly collections e.g.IReadOnlyList
.I would also expect
IReadOnlySet
to be featured in this page https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-types one way or another.Actual behavior
JsonSerializer.Deserialize<IReadOnlySet<int>>("[1]");
throwsNotSupportedException
and completely absent from https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/supported-collection-typesRegression?
Don't know.
Known Workarounds
Use
ISet
or similar.Configuration
Windows 11 Pro x64
Other information
No response
The text was updated successfully, but these errors were encountered: