-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fix json Serializer (all Proprties should be used) #73121
Conversation
…ttribute from the GetOrAddTypeGenerationSpec method?
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis |
Anyone a Idea how to access the OptionsAttribute from the GetOrAddTypeGenerationSpec method? |
/// Specifies that only members declared at the level of the supplied type's hierarchy should be considered. | ||
/// Inherited members are not considered. | ||
/// </summary> | ||
public bool OnlyDeclaredPropertiesOnInterfaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All new public APIs must follow the API review process before being added:
https://github.com/dotnet/runtime/blob/main/docs/project/api-review-process.md
To be honest though, I don't think should be made configurable, we should always serialize properties from base interfaces like we do with classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only thought cause it was mentioned here:
#41749 (comment)
If we remove the Configuration option, we also do not need the API Review process.
@@ -66,7 +71,11 @@ private void AddPropertiesAndParametersUsingReflection() | |||
// Walk through the inheritance hierarchy, starting from the most derived type upward. | |||
for (Type? currentType = Type; currentType != null; currentType = currentType.BaseType) | |||
{ | |||
PropertyInfo[] properties = currentType.GetProperties(BindingFlags); | |||
var bindingFlags = BindingFlagsDeclaredOnly; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to use different binding flags for interface types? The reason the bug exists is because currentType.BaseType
always returns null for interface types so the for loop will only iterate once and not consider any base interfaces. What I would recommend doing instead is tweaking the loop so that it iterates over currentType.GetInterfaces()
in cases where currentType
is an interface specifically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think that is faster? What is if some interfaces define the same properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've not yet tested the change. I need to check how to compile files of the .net runtime repo. I only wanted to know at first if fixing it like this would be okay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @eiriktsarpalis here, removing DeclaredOnly might end up showing same properties couple of times.
I'm curious if we could simplify this code or repeat what other serializers do (i.e. what does XmlSerializer do)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not simply remove it. I only removed it when the type is an interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the point is that we needn't use different binding flags when the type is an interface. The logic should follow classes in explicitly walking the type hierarchy and resolve any ambiguity following the same logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think that is faster? What is if some interfaces define the same properties?
It's not an issue of performance. The CacheMember method applies its own logic to resolve conflicts and for the sake of consistency the same logic should be followed in interface.
That being said, one thing that needs to be taken into account is diamond ambiguity which is not possible in class hierarchies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should see what other serializers are doing and match this behavior
I think this change is less trivial than one might have initially anticipated. Interfaces have concerns that are not directly applicable to class hierarchies, consider the following example: JsonSerializer.Serialize<IDiamond>(someValue); // what JSON contract should IDiamond have?
public interface IBase
{
[JsonPropertyOrder(1)]
int X { get; set; }
[JsonPropertyOrder(2)]
int Y { get; set; }
}
public interface IDerived1 : IBase
{
[JsonPropertyOrder(0)]
[JsonPropertyName("CustomName1")]
int IBase.Y
{
get => 42;
set { }
}
}
public interface IDerived2 : IBase
{
[JsonIgnore]
[JsonPropertyName("CustomName2")]
int IBase.Y
{
get => -1;
set { }
}
}
public interface IDiamond : IDerived1, IDerived2
{
} Interfaces support default interface members and admit multiple inheritance, so from the perspective of a serializer computing the JSON contract for It is not clear to me what the right solution might be here, and it might be the case that your approach (ignoring any attributes higher up in the hierarchy) is the simple and right thing to do. That being said, we are currently busy wrapping up development for .NET 7, so the margin for error is very small. I would recommend we close this PR and revisit once development for .NET 8 commences. It will give us the opportunity to more holistically address all the virtual member issues that the library has been facing, such as #50078 and #66900. cc @bartonjs @GrabYourPitchforks who might like this example. |
The only proper resolution would be to get rid of
I think this attribute should be marked as deprecated, because it promotes writing non-compliant applications. But that's another discussion 😄 If you really want to "properly" deal with these attributes, then it would make sense that the derived object takes precedence. But people are already shooting themselves in the foot if they rely on the order of the JSON properties and/or redefine the JSON property name in a derived class. |
I don't think handling of interfaces with same property names all with json serializer attributes are a weird special case, wich could not automaticaly be solved. I think the simples approach to read all of the inherited properties in a interface would solve 99% of the cases |
I don't like that it was moved to NET8. |
but also multiple JsonpropertyName attributes are a problem |
The issue backlog for System.Text.Json is fairly substantial, so we have to be strategic about what issues we do address in each release. Unfortunately, this means that legitimate bugs often get punted across releases. While I would like to see this fixed for .NET 7, as already mentioned, there are important open design questions to be addressed. Given that we are about lock down development for .NET 7, there is not enough leeway to do this.
That's quite possibly true, but any good design needs to account for 100% of the cases (which might include being explicit about which scenaria we don't want to support). Further to your point, I would contend that 99% of use cases define DTOs for their serialization contracts that don't employ interfaces or inheritance -- but that doesn't mean we shouldn't be consistent about the 1% that does use interface hierarchies. |
This pull request has been automatically marked |
I'm going to close this PR, per earlier feedback we need to think closer about semantics w.r.t. multiple inheritance conflict resolution before we fix this. Thank you for your contribution! |
See : #41749, #73021
Needs still work for Json Generator and Tests