Skip to content

Conversation

@layomia
Copy link
Contributor

@layomia layomia commented Sep 15, 2020

Fixes issue in .NET 6.0/master following #41679. The issue is not present in .NET 5.

@layomia layomia added this to the 5.0.0 milestone Sep 15, 2020
@layomia layomia self-assigned this Sep 15, 2020
@layomia layomia changed the title Ensure Read/WriteWithNumberHandling() not called for collection number-elements with custom converters Ensure ReadWithNumberHandling() not called for collection number-elements with custom converters Sep 15, 2020
@layomia layomia changed the title Ensure ReadWithNumberHandling() not called for collection number-elements with custom converters Ensure ReadWithNumberHandling() not called for custom converters of numeric-typed collection elements Sep 15, 2020
@jozkee
Copy link
Member

jozkee commented Sep 15, 2020

Instead of throwing, why not just normally forward the reader into the converter in case that it is not built-in? This implies not calling ReadNumberWithCustomHandling for such case.

JsonConverter converter = Options.GetConverter(elementType);
return converter.IsInternalConverterForNumberType;
}
catch (NotSupportedException)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we detect this without having to catch the exception?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to just call the custom converter and if it doesn't know about quoted numbers, then let it throw what it would have otherwise (likely a reader exception re-thrown as JsonException).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the custom converter is fine with me.

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to just call the custom converter and if it doesn't know about quoted numbers, then let it throw what it would have otherwise (likely a reader exception re-thrown as JsonException).

Why? I haven't fully thought through the scenario or use case, so definitely not pushing back, but just curious what is the benefit to the caller? Could such a behavior be confusing or would it, in fact, match their expectation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, the expectation when users provide custom converters is that they are called. The exception is when JsonIgnoreCondition.WhenWritingNull/Default/IgnoreNullValues is active and the value is null/default.

The initial concern was that it didn't make sense to specify both custom handling and a custom converter for a type/property since the custom handling would not be honored (by the serializer). Thus, logic was added to throw when both custom handling and a custom handling were provided. The fix would be to remove either the custom number handling or the custom converter. The problem with this is highlighted in #42239 (comment).

The current restriction makes it impossible to have a custom converter for a property on a non-owned type which has number handling specified

The downside to removing the restriction on custom converters is that users can now specify an option which will not be honored. The upside is that we maintain the behavior that a custom converter can take over the (de)serialization of any type, even if parts of the object graph is non-owned.

Copy link
Contributor

@ahsonkhan ahsonkhan Sep 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.

The current restriction makes it impossible to have a custom converter for a property on a non-owned type which has number handling specified

This is a great point, which helps motivate the change you are suggesting, for sure. However, doesn't a local attribute have precedence over a global converter?

Have we gotten any customer feedback around this, to validate that the current restriction is problematic?

What is the principles behind when an option is allowed to coincide with converters, and overrides its behavior, and when is it not allowed or ignored, with the converter being prioritized? Can we come up with and articulate a design rationale for why we have certain exceptions (like the ones you listed)? What is common/special about those?

I think it will be valuable to enumerate the design and precedence more broadly, across all options and permutations with converters. Should a local (or maybe even global) custom converter always take precedence, regardless of the other options, aside from converters in higher order of precedence?

@steveharter
Copy link
Contributor

steveharter commented Sep 15, 2020

If someone has a custom converter for a number and wants to manually support quoted numbers (by reading or writing JsonTokenType.String) is this possible (with the new number handling option set), or is this now prevented with InvalidOperationException? Since it was possible in the past to do this (AFAIK), we shouldn't close that option IMO just because the new number handling feature is turned on for non-custom converters.

This means that a custom converter for an Int32, for example, would throw if it doesn't support or know about quoted. Likely the exception would be thrown by the reader when the custom converter calls reader.GetInt32() which will be re-thrown as JsonException.

@ahsonkhan
Copy link
Contributor

The issue is not present in .NET 5.

So, is this PR for the 6.0 milestone?

@layomia layomia modified the milestones: 5.0.0, 6.0.0 Sep 17, 2020
@layomia
Copy link
Contributor Author

layomia commented Sep 17, 2020

Yes @ahsonkhan, for 6.0.

@layomia
Copy link
Contributor Author

layomia commented Sep 20, 2020

If someone has a custom converter for a number and wants to manually support quoted numbers (by reading or writing JsonTokenType.String) is this possible (with the new number handling option set), or is this now prevented with InvalidOperationException? Since it was possible in the past to do this (AFAIK), we shouldn't close that option IMO just because the new number handling feature is turned on for non-custom converters.

This means that a custom converter for an Int32, for example, would throw if it doesn't support or know about quoted. Likely the exception would be thrown by the reader when the custom converter calls reader.GetInt32() which will be re-thrown as JsonException.

We were throwing InvalidOperationException when a property or type had JsonNumberHandlingAttribute and there was also a custom converter. The exception would not be thrown when there was no `JsonNumberHandlingAttribute`` on the type/property but there was a converter and number handling specified globally on the options instance.

The current restriction makes it impossible to have a custom converter for a property on a non-owned type which has number handling specified:

public class NonOwnedType
{
    [JsonNumberHandling(JsonNumberHandling.WriteAsString)]
    public int Num { get; set; }
}

JsonSerializer.Serialize(new NonOwnedType()); // Works okay: {"Num":"0"}

var options = new JsonSerializerOptions { Converters = new CustomIntConverter() };

// InvalidOperationException thrown at warm up without workaround except type owner removing attribute on `Num` property,
// or using custom converter for `NonOwnedType` itself.
JsonSerializer.Serialize(new NonOwnedType(), options);

I'm not sure that this will be a popular scenario that we have to fix for 5.0.

I agree we should remove restrictions with custom converters and this feature. If both custom number handling and a custom converter are provided, we'll just call the converters regular Read and Write method, and the converter is free to do whatever. In the future we can provide a way to inspect per-property handling in custom converters, alongside other per-property features. The new commit updates the logic and tests appropriately.

Copy link
Contributor

@steveharter steveharter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with the converter restrictions removed.

@ericstj
Copy link
Member

ericstj commented Jan 22, 2021

@layomia is this PR still relevant?

@layomia
Copy link
Contributor Author

layomia commented Jan 22, 2021

@ericstj yes - I'll finish this PR and merge it.

Base automatically changed from master to main March 1, 2021 09:07
@layomia
Copy link
Contributor Author

layomia commented Mar 10, 2021

Will circle back to this and merge it.

@layomia layomia closed this Mar 10, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Apr 9, 2021
@layomia layomia deleted the number_handling branch May 18, 2021 07:03
@layomia layomia restored the number_handling branch July 27, 2021 03:28
@layomia layomia reopened this Jul 27, 2021
@layomia layomia closed this Aug 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants