ipfs/kubo · error

unable to parse duration, expected a duration string or a fl

Error message

unable to parse duration, expected a duration string or a float, but got %T

What it means

The Duration config type accepts a JSON string parsed by time.ParseDuration (e.g. "300ms", "1h") or a JSON number interpreted as seconds (float). Any other JSON type (object, array, boolean, null in the default branch) cannot be interpreted and produces this type error.

Source

Thrown at config/types.go:317

func (d *Duration) UnmarshalJSON(b []byte) error {
	var v any
	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 fmt.Errorf("unable to parse duration, expected a duration string or a float, but got %T", v)
	}
}

var (
	_ json.Unmarshaler = (*Duration)(nil)
	_ json.Marshaler   = (*Duration)(nil)
)

// OptionalInteger represents an integer that has a default value
//
// When encoded in json, Default is encoded as "null".
type OptionalInteger struct {
	value *int64
}

// NewOptionalInteger returns an OptionalInteger from a int64.
func NewOptionalInteger(v int64) *OptionalInteger {
	return &OptionalInteger{value: &v}

View on GitHub (pinned to 329838acdf)

Solutions

  1. Use a duration string: {"Timeout": "1h30m"}.
  2. Or use a plain number of seconds: {"Timeout": 3600}.
  3. Check the producing code so time.Duration-like values are serialized as strings or seconds, not objects.

Example fix

// before
{"Provider.Timeout": {"secs": 60, "nanos": 0}}
// after
{"Provider.Timeout": "60s"}
Defensive patterns

Strategy: validation

Validate before calling

func validDuration(v any) error {
    switch t := v.(type) {
    case string:
        _, err := time.ParseDuration(t)
        return err
    case float64:
        return nil // seconds
    default:
        return fmt.Errorf("duration must be string or seconds number, got %T", v)
    }
}

Type guard

func isDurationValue(v any) bool {
    switch t := v.(type) {
    case string:
        _, err := time.ParseDuration(t)
        return err == nil
    case float64:
        return true
    }
    return false
}

Try / catch

if err := json.Unmarshal(data, &cfg); err != nil {
    if strings.Contains(err.Error(), "unable to parse duration") {
        // fix the field to "300ms"-style string or seconds number
    }
}

Prevention

When it happens

Trigger: Unmarshaling a Duration field with a non-string/non-number JSON value, e.g. {"Timeout": {"secs": 5}} or {"Timeout": true}.

Common situations: Generating config programmatically and emitting a duration as an object (Go time.Duration marshaled as nanosecond integer is fine, but a custom struct is not); hand-editing with a boolean or nested value; YAML-to-JSON conversion emitting a map.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of ipfs/kubo@329838acdf (2026-09-03). Data as JSON: /api/errors/e5b1fb5d34596c6d. Report an issue: GitHub.