-
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
System.Text.Json does not support TypeConverters #38812
Comments
an example of complex type that has to be converted to string
converter
registered with |
Tagging subscribers to this area: @safern |
The easiest way to reproduce the issue is to use NodaTime 3.0 which has type converter support added. This class makes a good test harness.. public class NodaValues
{
public NodaValues()
{
try
{
DateTimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault();
}
catch (DateTimeZoneNotFoundException)
{
DateTimeZone = DateTimeZoneProviders.Tzdb["America/New_York"];
}
Instant = SystemClock.Instance.GetCurrentInstant();
ZonedDateTime = Instant.InZone(DateTimeZone);
OffsetDateTime = Instant.WithOffset(ZonedDateTime.Offset);
OffsetDate = new OffsetDate(LocalDate, Offset);
OffsetTime = new OffsetTime(LocalTime, Offset);
Interval = new Interval(Instant,
Instant
.PlusTicks(TimeSpan.TicksPerDay)
.PlusTicks(TimeSpan.TicksPerHour)
.PlusTicks(TimeSpan.TicksPerMinute)
.PlusTicks(TimeSpan.TicksPerSecond)
.PlusTicks(TimeSpan.TicksPerMillisecond));
Period = Period.Between(LocalDateTime,
Interval.End.InZone(DateTimeZone).LocalDateTime, PeriodUnits.AllUnits);
}
public AnnualDate AnnualDate => new AnnualDate(DateTime.Now.Month, DateTime.Now.Day);
public DateTimeZone DateTimeZone { get; }
public Duration Duration => Interval.Duration;
public Instant Instant { get; }
public Interval Interval { get; }
public LocalDate LocalDate => ZonedDateTime.Date;
public LocalDateTime LocalDateTime => ZonedDateTime.LocalDateTime;
public LocalTime LocalTime => ZonedDateTime.TimeOfDay;
public Offset Offset => ZonedDateTime.Offset;
public OffsetDate OffsetDate { get; }
public OffsetDateTime OffsetDateTime { get; }
public OffsetTime OffsetTime { get; }
public Period Period { get; }
public YearMonth YearMonth => new YearMonth(DateTime.Now.Year, DateTime.Now.Month);
public ZonedDateTime ZonedDateTime { get; }
} |
Another thing when serializing and deserializing dictionaries that have a key you should also check if a type converter to string exists for the key and if so use it. Currently it blows up if you return a Dictionary<DateTime, string> even though DateTIme has a TypeConverter to string that could be used. |
@layomia where does this issue sit prioritization wise? It's a pretty big miss in the json library to not implicitly check for type converters and use them when relevant. |
We've added support for more primitives as dictionary keys in 5.0 (#38056), including The lookup and invocation of these Can you tell me more about why |
@layomia see responses inline
Thank you for the update. That is good news and definitely a step in the right direction. I did verify it in preview8 of the v5 SDK.
I put together a sample repository that benchmarks the type converter operations and it was running anywhere from 64ms to 45ms inside my VM on a Surface Pro 5. As far as the performance goals of System.Text.Json let's just say we have to agree to disagree here..... System.Text.Json performs way too close to Json.NET and not anywhere close to UTF8Json for that to continue to be the mantra if we're being honest. Outperforming Json.NET should definitely be the goal and you've hit that. Unfortunately feature parity still makes me prefer to use Json.NET over System.Text.Json 100% of the time because the gap in performance is not so substantial to close the feature parity gap. In .NET 5 I want to cut over to web assembly but that exclusively uses System.Text.Json so now the gaps are starting to become glaring.
TypeConverters don't need to be used other than they have been around for ages and work wonderfully as evidenced by how fluidly Json.NET handles custom structs vs System.Text.Json. As far as which types don't work that would be all of them here is the difference with the LocalTime struct... Json.NET which uses the TypeConverter to string
System.Text.Json which fumbles across all the properties on the struct
I have added a JsonConverter for LocalDate that fixes the issue to and from the wire when used explicitly as a scalar property of an object. Unfortunately even with the TypeConverter registered it still blows up when you try to produce a dictionary with LocalDate as a key so I added a DictionaryKeyTypeConverter so yes all of that does work. The problem is what happens when you make IDictionary, IReadOnlyDictionary, ReadOnlyDictionary, IImmutableDictionary, or ImmutableDictionary parameters? I get you don't want to use TypeConverters because they are viewed as legacy but in the case of only using them explicitly to and from strings to solve issues like dictionary keys or if the object is a struct and is missing a JsonConverter seems like a no brainer to me. |
@buvinghausen That's not the reason why we didn't took the approach of relying on Please see the spec for extending support of dictionary keys. The results are form very early steps in the feature development but you can notice that the TypeConverter approach for KeyConverter:
TypeConverter:
|
@jozkee thank you for the expedient follow up. So how do I plumb the JsonConverter into getting picked up as a key converter? |
As @layomia mentioned, we could expose the mechanism to use |
@jozkee so here comes the compromise.... Since API proposals and reviews take time is there any chance we could meet at a compromise which is if no key converter is registered then as a last ditch fail over to checking for a type converter and it's ability to convert to and from a string and use it rather than throw the exception? That would leave the existing API as is and the only people who are paying the performance hit are those who don't have a key converter registered for the type they are using. Once the API to register key converters is live I then can register them and stop paying the performance tax from boxing. |
The
We understand that different scenarios/benchmarks have different perf characteristics and results/comparisons will vary depending on what is being tested. Performance is an area of continuous improvement for System.Text.Json. We've made many improvements to the library in 5.0 wrt to both perf and features. The goal is to maintain a good balance/trade-off between perf and features/usability. This involves maintaining a performance-first architecture, and taking a dependency on There is an opportunity to improve perf in more scenarios (Blazor WASM, Xamarin iOS/Android) with the JSON compile time code-gen feature coming in 6.0.
As a framework, we can't just introduce behavior like this flippantly, because it becomes part of the contract that callers expect and even though it won't be an API breaking change if we change our mind after designing the end-to-end, it will be a behavioral breaking change. Not to mention the level of testing that would have to go in it. Either way we go (supporting The recommendation for anyone dependent on |
I'm seeing the performance argument being used in this one, but wouldn't it only be a hit in case type converters are actually being used? If no type converters exist for a given type, then performance should stay the same, as it is not calling into the various legacy methods there. At the same time, if people want to rely on type converters, it is on them to evaluate the performance impact. The benefit of the type converter approach is that it is extremely general purpose and used for various different things and frameworks. Since the interface is generic, it can be translated to pretty much any "custom conversion" interface in many libraries seamlessly. There should at least be a |
@julealgon that was essentially my point was the only time the perf penalty was paid was if it was being used as a last resort and that if I wanted to side step I would wire up a JsonConverter. Unfortunately the team has been very adamant about making this transition exceptionally painful. See things like not wanting to add kebab-case or snake_case as examples or even the flat out refusal to support the data contract attributes even though those are all I ever use because of their portability across json.net and the data contract serializer for XML. |
Here's a intermediate solution to make any type having a
Use like this:
|
I have written something similar here #1761 it has been battle tested accross multiple projects migrated to system text json. |
Per feedback, adding built-in support for |
Describe the bug
I was used to have custom TypeConverter to convert complex types from/to strings
It was working when the serializator was Newtonsoft, now that you moved to internal dotnet serializator it doesn't work anymore
To Reproduce
Create a controller that returns a class with a complex type where you implemented a TypeConverter for (and registered using TypeDescriptor.AddAttributes )
Look the result of the controller and you'll see that the output will not be serialized according to the TypeConverter but will be serialized fully as a complex object
We will close this issue if:
Further technical details
Note that using the package
Microsoft.AspNetCore.Mvc.NewtonsoftJson
and doingservices.AddControllers().AddNewtonsoftJson()
(so basically restoring newtonsoft serializator) fixes this problem (basically bypass internal json serializator)The text was updated successfully, but these errors were encountered: