Skip to content

Commit 6c65a55

Browse files
authored
jsonpb: fix marshaling of Duration
Fix output for negative nanoseconds with zero second. Add validation on min/max seconds. Fixes #883.
1 parent b285ee9 commit 6c65a55

File tree

2 files changed

+35
-20
lines changed

2 files changed

+35
-20
lines changed

jsonpb/jsonpb.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
)
5858

5959
const secondInNanos = int64(time.Second / time.Nanosecond)
60+
const maxSecondsInDuration = 315576000000
6061

6162
// Marshaler is a configurable object for converting between
6263
// protocol buffer objects and a JSON representation for them.
@@ -211,19 +212,26 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeU
211212
// Any is a bit more involved.
212213
return m.marshalAny(out, v, indent)
213214
case "Duration":
214-
// "Generated output always contains 0, 3, 6, or 9 fractional digits,
215-
// depending on required precision."
216215
s, ns := s.Field(0).Int(), s.Field(1).Int()
216+
if s < -maxSecondsInDuration || s > maxSecondsInDuration {
217+
return fmt.Errorf("seconds out of range %v", s)
218+
}
217219
if ns <= -secondInNanos || ns >= secondInNanos {
218220
return fmt.Errorf("ns out of range (%v, %v)", -secondInNanos, secondInNanos)
219221
}
220222
if (s > 0 && ns < 0) || (s < 0 && ns > 0) {
221223
return errors.New("signs of seconds and nanos do not match")
222224
}
223-
if s < 0 {
225+
// Generated output always contains 0, 3, 6, or 9 fractional digits,
226+
// depending on required precision, followed by the suffix "s".
227+
f := "%d.%09d"
228+
if ns < 0 {
224229
ns = -ns
230+
if s == 0 {
231+
f = "-%d.%09d"
232+
}
225233
}
226-
x := fmt.Sprintf("%d.%09d", s, ns)
234+
x := fmt.Sprintf(f, s, ns)
227235
x = strings.TrimSuffix(x, "000")
228236
x = strings.TrimSuffix(x, "000")
229237
x = strings.TrimSuffix(x, ".000")

jsonpb/jsonpb_test.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,17 @@ var marshalingTests = []struct {
473473
{"Any with message and indent", marshalerAllOptions, anySimple, anySimplePrettyJSON},
474474
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
475475
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
476-
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3s"}`},
477-
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3, Nanos: 1e6}}, `{"dur":"3.001s"}`},
478-
{"Duration beyond float64 precision", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
479-
{"negative Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: -123, Nanos: -456}}, `{"dur":"-123.000000456s"}`},
476+
{"Duration empty", marshaler, &durpb.Duration{}, `"0s"`},
477+
{"Duration with secs", marshaler, &durpb.Duration{Seconds: 3}, `"3s"`},
478+
{"Duration with -secs", marshaler, &durpb.Duration{Seconds: -3}, `"-3s"`},
479+
{"Duration with nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
480+
{"Duration with -nanos", marshaler, &durpb.Duration{Nanos: -1e6}, `"-0.001s"`},
481+
{"Duration with large secs", marshaler, &durpb.Duration{Seconds: 1e10, Nanos: 1}, `"10000000000.000000001s"`},
482+
{"Duration with 6-digit nanos", marshaler, &durpb.Duration{Nanos: 1e4}, `"0.000010s"`},
483+
{"Duration with 3-digit nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
484+
{"Duration with -secs -nanos", marshaler, &durpb.Duration{Seconds: -123, Nanos: -450}, `"-123.000000450s"`},
485+
{"Duration max value", marshaler, &durpb.Duration{Seconds: 315576000000, Nanos: 999999999}, `"315576000000.999999999s"`},
486+
{"Duration min value", marshaler, &durpb.Duration{Seconds: -315576000000, Nanos: -999999999}, `"-315576000000.999999999s"`},
480487
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
481488
Fields: map[string]*stpb.Value{
482489
"one": {Kind: &stpb.Value_StringValue{"loneliest number"}},
@@ -549,15 +556,17 @@ func TestMarshalIllegalTime(t *testing.T) {
549556
pb proto.Message
550557
fail bool
551558
}{
552-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 0}}, false},
553-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 0}}, false},
554-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: -1}}, true},
555-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 1}}, true},
556-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 1000000000}}, true},
557-
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: -1000000000}}, true},
558-
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1}}, false},
559-
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: -1}}, true},
560-
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1000000000}}, true},
559+
{&durpb.Duration{Seconds: 1, Nanos: 0}, false},
560+
{&durpb.Duration{Seconds: -1, Nanos: 0}, false},
561+
{&durpb.Duration{Seconds: 1, Nanos: -1}, true},
562+
{&durpb.Duration{Seconds: -1, Nanos: 1}, true},
563+
{&durpb.Duration{Seconds: 315576000001}, true},
564+
{&durpb.Duration{Seconds: -315576000001}, true},
565+
{&durpb.Duration{Seconds: 1, Nanos: 1000000000}, true},
566+
{&durpb.Duration{Seconds: -1, Nanos: -1000000000}, true},
567+
{&tspb.Timestamp{Seconds: 1, Nanos: 1}, false},
568+
{&tspb.Timestamp{Seconds: 1, Nanos: -1}, true},
569+
{&tspb.Timestamp{Seconds: 1, Nanos: 1000000000}, true},
561570
}
562571
for _, tt := range tests {
563572
_, err := marshaler.MarshalToString(tt.pb)
@@ -607,8 +616,7 @@ func TestMarshalAnyJSONPBMarshaler(t *testing.T) {
607616
t.Errorf("an unexpected error occurred when marshalling Any to JSON: %v", err)
608617
}
609618
// same as expected above, but pretty-printed w/ indentation
610-
expected =
611-
`{
619+
expected = `{
612620
"@type": "type.googleapis.com/` + dynamicMessageName + `",
613621
"baz": [
614622
0,
@@ -623,7 +631,6 @@ func TestMarshalAnyJSONPBMarshaler(t *testing.T) {
623631
}
624632
}
625633

626-
627634
func TestMarshalWithCustomValidation(t *testing.T) {
628635
msg := dynamicMessage{RawJson: `{ "foo": "bar", "baz": [0, 1, 2, 3] }`, Dummy: &dynamicMessage{}}
629636

0 commit comments

Comments
 (0)