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

System.Text.Json: Add TimeSpanConverter #54186

Merged
merged 11 commits into from
Jul 7, 2021
Merged

Conversation

CodeBlanch
Copy link
Contributor

Fixes #29932

Description

Adds TimeSpan support to System.Text.Json JsonSerializer.

Uses Utf8Parser.TryParse & Utf8Formatter.TryFormat for the heavy lifting.

Public API Changes

    public static partial class JsonMetadataServices
    {
       public static System.Text.Json.Serialization.JsonConverter<System.TimeSpan> TimeSpanConverter { get { throw null; } }
    }

Considerations

This is using the constant ("c") format. I think the general short format ("g") could compact things a bit, but not sure if that would round-trip with Newtonsoft and it is culture-sensitive.

/cc @layomia @eiriktsarpalis @JamesNK

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Jun 14, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #29932

Description

Adds TimeSpan support to System.Text.Json JsonSerializer.

Uses Utf8Parser.TryParse & Utf8Formatter.TryFormat for the heavy lifting.

Public API Changes

    public static partial class JsonMetadataServices
    {
       public static System.Text.Json.Serialization.JsonConverter<System.TimeSpan> TimeSpanConverter { get { throw null; } }
    }

Considerations

This is using the constant ("c") format. I think the general short format ("g") could compact things a bit, but not sure if that would round-trip with Newtonsoft and it is culture-sensitive.

/cc @layomia @eiriktsarpalis @JamesNK

Author: CodeBlanch
Assignees: -
Labels:

area-System.Text.Json, new-api-needs-documentation

Milestone: -

@eiriktsarpalis
Copy link
Member

CI test failures are unrelated. @layomia has your feedback been addressed?

Copy link
Contributor

@layomia layomia left a comment

Choose a reason for hiding this comment

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

LGTM, but we should remove the 'g' format fallback, keeping in line with the implementation for other primitives that only support one popular format (e.g. ISO 8601 for DateTime). This way there's no confusion about what formats are supported/unsupported. If we get feedback later requesting other formats, we can re-add the fallback(s).

@CodeBlanch
Copy link
Contributor Author

Updated to remove the 'g' fallback.

@layomia layomia self-requested a review July 1, 2021 19:16
Copy link
Contributor

@layomia layomia left a comment

Choose a reason for hiding this comment

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

Thanks for removing the fallback. Just a couple minor comments left to address, then this PR is good to merge.

bool result = Utf8Formatter.TryFormat(value, output, out int bytesWritten, 'c');
Debug.Assert(result);

writer.WriteStringValue(output.Slice(0, bytesWritten));
Copy link
Contributor

@layomia layomia Jul 1, 2021

Choose a reason for hiding this comment

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

[Non-blocking for this PR] When #54254 goes in, we should consider updating this to use the new Utf8JsonWriter.WriteRawValue API so we can avoid logic that checks whether the input payload needs to be escaped. We'd first need to quote the formatted value.

Copy link
Contributor

@layomia layomia left a comment

Choose a reason for hiding this comment

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

Thanks, @CodeBlanch! LGTM, with a few suggestions for a follow up PR.

Test failures are unrelated to the changes in this PR.

@@ -117,12 +117,6 @@ public static bool IsValidDateTimeOffsetParseLength(int length)
return IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsValidDateTimeOffsetParseLength(long length)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why delete this helper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It wasn't being used anymore so I cleaned it up. The code that was calling it (in Utf8JsonReader.TryGet) is now calling IsInRangeInclusive directly so it can pass in the max length based on encoded string or not. You want me to put it back with the other feedback items for a follow-up?

{
value = default;
return false;
}

Debug.Assert(sequenceLength <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
Span<byte> stackSpan = stackalloc byte[(int)sequenceLength];

Span<byte> stackSpan = stackalloc byte[_stringHasEscaping ? JsonConstants.MaximumEscapedDateTimeOffsetParseLength : JsonConstants.MaximumDateTimeOffsetParseLength];
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for these changes. Searching for "stackalloc byte" in the STJ.csproj shows there might be a few more places that would benefit from this pattern, particularly in JsonReaderHelper.*.cs. We'd take a follow up PR to address this if you're up for it!

@@ -215,6 +214,7 @@ public static IEnumerable<object[]> InvalidISO8601Tests()
yield return new object[] { "\"1997-07-16T19:20:30.45555555550000000\"" };
yield return new object[] { "\"1997-07-16T19:20:30.45555555555555555\"" };
yield return new object[] { "\"1997-07-16T19:20:30.45555555555555555555\"" };
yield return new object[] { "\"1997-07-16T19:20:30.4555555555555555+01:300\"" };
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we have DateTimeOffset failure tests for leading whitespace. We'd take a follow up to add these.

@@ -303,6 +306,9 @@ public static void ValueFail()
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DateTimeOffset>(unexpectedString));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DateTimeOffset?>(unexpectedString));

Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<TimeSpan>(unexpectedString));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<TimeSpan?>(unexpectedString));
Copy link
Contributor

Choose a reason for hiding this comment

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

Due to other logic, we should be fine, but a few more nullable TimeSpan tests would be good. E.g. deserializing null into a non-nullable TimeSpan should fail, but should be fine for nullable TimeSpan?.

[MemberData(nameof(JsonDateTimeTestData.InvalidISO8601Tests), MemberType = typeof(JsonDateTimeTestData))]
public static void TryGetDateTime_HasValueSequence_False(string testString)
{
static void test(string testString, bool isFinalBlock)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit

Suggested change
static void test(string testString, bool isFinalBlock)
static void Test(string testString, bool isFinalBlock)

@layomia layomia added this to the 6.0.0 milestone Jul 7, 2021
@layomia layomia merged commit d5f9544 into dotnet:main Jul 7, 2021
@CodeBlanch CodeBlanch deleted the json-timespan2 branch July 7, 2021 17:26
eiriktsarpalis pushed a commit to CodeBlanch/runtime that referenced this pull request Jul 16, 2021
stephentoub pushed a commit that referenced this pull request Jul 18, 2021
* Follow-up feedback from #54186.

* Code review.

* Code review.

* Dedicated constant for the max number of chars permitted to be allocated on the stack.
ycrumeyrolle added a commit to uruk-project/Jwt that referenced this pull request Jul 22, 2021
ycrumeyrolle added a commit to uruk-project/Jwt that referenced this pull request Aug 3, 2021
* Constant `stackalloc` can be optimized by the JIT.
dotnet/runtime#54186 (comment)

* Fix ESXXX KID computation cause by incorrect canonicalized JWK size ('crv' was computed as base64-encoded)

* `EllipticalCurve` is now a class instead of a readonly struct.
This reduce the cost of struct copy.
@ghost ghost locked as resolved and limited conversation to collaborators Aug 6, 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.

JsonSerializer support for TimeSpan in 3.0?
4 participants