How to unmarshal JSON into durations?
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")
}
}
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)
}
https://play.golang.org/p/Zm6hpNR-ZJ2