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

Set json encoding precision #162

Merged
merged 5 commits into from Feb 10, 2022
Merged

Set json encoding precision #162

merged 5 commits into from Feb 10, 2022

Conversation

mfridman
Copy link
Member

@mfridman mfridman commented Feb 8, 2022

Crude implementation to fix #158.

Alternatively we could switch on TimePrecision to set the precision, but this might be confusing and deferring to strconv.FormatFloat(...) with -1 seems like the right approach.

Also, what happens if someone sets the default to minutes .. should we need to account for that?

@oxisto
Copy link
Collaborator

oxisto commented Feb 8, 2022

Crude implementation to fix #158.

Alternatively we could switch on TimePrecision to set the precision, but this might be confusing and deferring to strconv.FormatFloat(...) with -1 seems like the right approach.

I am thinking whether we could calculate the float precision. Need to spin my head around that

Also, what happens if someone sets the default to minutes .. should we need to account for that?

No, float precision 0 should be the "minimum" precision (seconds).

parser_test.go Outdated Show resolved Hide resolved
parser_test.go Outdated Show resolved Hide resolved
@oxisto
Copy link
Collaborator

oxisto commented Feb 8, 2022

I have created an old school diff for a calculation approach here:

diff --git a/parser_test.go b/parser_test.go
index 87a18e5..a224fc9 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -657,24 +657,24 @@ func TestNewNumericDateMarshalJSON(t *testing.T) {
 		precision time.Duration
 	}{
 		{time.Unix(5243700879, 0), "5243700879", time.Second},
-		{time.Unix(5243700879, 0), "5243700879.000001", time.Millisecond},
+		{time.Unix(5243700879, 0), "5243700879.000", time.Millisecond},
 		{time.Unix(5243700879, 0), "5243700879.000001", time.Microsecond},
-		{time.Unix(5243700879, 0), "5243700879.000001", time.Nanosecond},
+		{time.Unix(5243700879, 0), "5243700879.000000954", time.Nanosecond},
 		//
 		{time.Unix(4239425898, 0), "4239425898", time.Second},
-		{time.Unix(4239425898, 0), "4239425898", time.Millisecond},
-		{time.Unix(4239425898, 0), "4239425898", time.Microsecond},
-		{time.Unix(4239425898, 0), "4239425898", time.Nanosecond},
+		{time.Unix(4239425898, 0), "4239425898.000", time.Millisecond},
+		{time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond},
+		{time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond},
 		//
 		{time.Unix(0, 1644285000210402000), "1644285000", time.Second},
-		{time.Unix(0, 1644285000210402000), "1644285000.2099998", time.Millisecond},
+		{time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond},
 		{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond},
-		{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Nanosecond},
+		{time.Unix(0, 1644285000210402000), "1644285000.210402012", time.Nanosecond},
 		//
 		{time.Unix(0, 1644285315063096000), "1644285315", time.Second},
 		{time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond},
 		{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond},
-		{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Nanosecond},
+		{time.Unix(0, 1644285315063096000), "1644285315.063096046", time.Nanosecond},
 	}

 	for i, tc := range tt {
diff --git a/types.go b/types.go
index a5ef506..0e83229 100644
--- a/types.go
+++ b/types.go
@@ -49,10 +49,8 @@ func newNumericDateFromSeconds(f float64) *NumericDate {
 // MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch
 // represented in NumericDate to a byte array, using the precision specified in TimePrecision.
 func (date NumericDate) MarshalJSON() (b []byte, err error) {
-	prec := -1
-	if TimePrecision == time.Second {
-		prec = 0
-	}
+	prec := int(math.Log10(float64(time.Second) / float64(TimePrecision)))
+
 	f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second)

 	return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil

@@ -18,12 +18,10 @@ func TestNumericDate(t *testing.T) {

jwt.TimePrecision = time.Microsecond

raw := `{"iat":1516239022,"exp":1516239022.12345}`
raw := `{"iat":1516239022.000000,"exp":1516239022.123450}`
Copy link
Member Author

Choose a reason for hiding this comment

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

I suppose this makes sense.

I personally haven't encountered a need to set the precision in any of the projects I've worked on, and second granularity (without precision) has been sufficient.

Maybe we're overthinking it, and the alternative solution is to always set the default precision to 0.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes it does make sense. We are setting precision to microsecond and are serialising as microsecond. Sounds good for me.

Copy link
Collaborator

@oxisto oxisto left a comment

Choose a reason for hiding this comment

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

Minor things, but I like it :)

@@ -18,12 +18,10 @@ func TestNumericDate(t *testing.T) {

jwt.TimePrecision = time.Microsecond

raw := `{"iat":1516239022,"exp":1516239022.12345}`
raw := `{"iat":1516239022.000000,"exp":1516239022.123450}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes it does make sense. We are setting precision to microsecond and are serialising as microsecond. Sounds good for me.

types.go Outdated Show resolved Hide resolved
@oxisto
Copy link
Collaborator

oxisto commented Feb 9, 2022

@mfridman Do you remember who had brought up the original issue of float precision (not the issue in the PR but the original move from int to float)? I would need to look it up. Might be interesting to get some feedback from them, to see if their use case still works. My gut feeling is that this won't break anything but be potentially more precise.

@mfridman
Copy link
Member Author

mfridman commented Feb 9, 2022

@mfridman Do you remember who had brought up the original issue of float precision (not the issue in the PR but the original move from int to float)? I would need to look it up. Might be interesting to get some feedback from them, to see if their use case still works. My gut feeling is that this won't break anything but be potentially more precise.

Are you thinking of this merged PR?

@oxisto
Copy link
Collaborator

oxisto commented Feb 9, 2022

@mfridman Do you remember who had brought up the original issue of float precision (not the issue in the PR but the original move from int to float)? I would need to look it up. Might be interesting to get some feedback from them, to see if their use case still works. My gut feeling is that this won't break anything but be potentially more precise.

Are you thinking of this merged PR?

This one: #9 it was @dunglas.

@dunglas
Copy link
Contributor

dunglas commented Feb 9, 2022

@oxisto the problem I fixed was when decoding JWTs issued by other libraries such as lcobucci/jwt, which use floating-point numbers by default.

This change looks good to me.

@mfridman mfridman merged commit 279dd19 into main Feb 10, 2022
@mfridman mfridman deleted the GH-158 branch February 10, 2022 02:54
oxisto pushed a commit to moneszarrugh/jwt that referenced this pull request Feb 21, 2023
oxisto pushed a commit to twocs/jwt that referenced this pull request Mar 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NumericDate marshals to JSON with decimals for large dates
3 participants