What is the idiomatic way to unmarshal into time.Duration
in Go? How can I make use of time.ParseDuration
?
Asked
Active
Viewed 2.1k times
32
-
For types defined outside of your project that do not implement the Unmarshaler interface, or do but in a way that is not sufficient for your requirements, you should define a "wrapper" type and have it implement the Unmarshaler interface so that it meets your needs. – mkopriva Jan 01 '18 at 16:17
2 Answers
49
The lack of JSON marshaling and unmarshaling methods on time.Duration
was an unfortunate oversight. This should hopefully be resolved in Go2 (see issue #10275).
You can, however, define your own type around time.Duration
that supports marshaling to the string representation of the duration and unmarshaling from either the numeric or string representations. Here is an example of such an implementation:
package main
import (
"encoding/json"
"errors"
"fmt"
"time"
)
type Duration struct {
time.Duration
}
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
type Message struct {
Elapsed Duration `json:"elapsed"`
}
func main() {
msgEnc, err := json.Marshal(&Message{
Elapsed: Duration{time.Second * 5},
})
if err != nil {
panic(err)
}
fmt.Printf("%s\n", msgEnc)
var msg Message
if err := json.Unmarshal([]byte(`{"elapsed": "1h"}`), &msg); err != nil {
panic(err)
}
fmt.Printf("%#v\n", msg)
}
-
1
-
3
-
1why is `Duration` a `struct` of `time.Duration` instead of as in the other answer: i.e. `type Duration time.Duration`? Thank you in advance :) – Nick Brady Mar 20 '19 at 00:36
-
One thing I noticed when implementing this solution but with a type alias was that you lose some of the access to existing methods on the `time.Duration` type. So one example was that when implementing `MarshalJSON` the `String` method no longer existed. – James Durand Sep 09 '19 at 01:31
29
Just to extend the previous answer. There is another way (very close to Tim's)
type Duration time.Duration
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
*d = Duration(time.Duration(value))
return nil
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return errors.New("invalid duration")
}
}

Marcel
- 1,509
- 1
- 17
- 39

Alexander Polyakov
- 301
- 3
- 3