From ee4b1bfe63371161c964e11148cd71e4659e3172 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Wed, 4 Nov 2026 12:01:45 -0500 Subject: [PATCH] fix(xtime): handle a JSON null in Duration.UnmarshalJSON Co-Authored-By: Claude Signed-off-by: Arpit Jain --- xtime/time.go | 13 +++++++++++-- xtime/time_unmarshal_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/xtime/time.go b/xtime/time.go index e27ba630..6263c159 100644 --- a/xtime/time.go +++ b/xtime/time.go @@ -82,10 +82,19 @@ func (d *Duration) UnmarshalYAML(value *yaml.Node) error { // UnmarshalJSON implements json.Unmarshaler func (d *Duration) UnmarshalJSON(bs []byte) error { - if len(bs) <= 2 { + // A null leaves the value alone, as it does for a type that has no + // Unmarshaler, and as UnmarshalYAML already does. + if string(bs) == "null" { return nil } - dur, err := ParseDuration(string(bs[1 : len(bs)-1])) + var s string + if err := json.Unmarshal(bs, &s); err != nil { + return err + } + if s == "" { + return nil + } + dur, err := ParseDuration(s) if err != nil { return err } diff --git a/xtime/time_unmarshal_test.go b/xtime/time_unmarshal_test.go index 8062d5db..ea4eeaeb 100644 --- a/xtime/time_unmarshal_test.go +++ b/xtime/time_unmarshal_test.go @@ -160,3 +160,30 @@ durationPointer: 168h0m1s t.Errorf("JSON round-trip mismatch: got %+v", back) } } + +func TestDuration_UnmarshalJSONNull(t *testing.T) { + type s struct { + Dur Duration `json:"dur" yaml:"dur"` + } + + // A JSON null leaves the field alone, as it does for a type with no + // Unmarshaler, and as the YAML side of this type already does. + v := s{Dur: Duration(time.Hour)} + if err := json.Unmarshal([]byte(`{"dur":null}`), &v); err != nil { + t.Fatalf("json null: %v", err) + } + if v.Dur.D() != time.Hour { + t.Errorf("json null overwrote the field: got %v, want 1h0m0s", v.Dur.D()) + } + var y s + if err := yaml.Unmarshal([]byte("dur: null\n"), &y); err != nil { + t.Fatalf("yaml null: %v", err) + } + + // A JSON number is not a duration. Reading it as zero hides the + // mistake from whoever wrote the config. + var n s + if err := json.Unmarshal([]byte(`{"dur":12}`), &n); err == nil { + t.Errorf("expected an error for a JSON number, got %v", n.Dur.D()) + } +}