{"record":{"id":"e5b1fb5d34596c6d","repo":"ipfs/kubo","slug":"unable-to-parse-duration-expected-a-duration-stri","errorCode":null,"errorMessage":"unable to parse duration, expected a duration string or a float, but got %T","messagePattern":"unable to parse duration, expected a duration string or a float, but got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/types.go","lineNumber":317,"sourceCode":"\nfunc (d *Duration) UnmarshalJSON(b []byte) error {\n\tvar v any\n\tif err := json.Unmarshal(b, &v); err != nil {\n\t\treturn err\n\t}\n\tswitch value := v.(type) {\n\tcase float64:\n\t\td.Duration = time.Duration(value)\n\t\treturn nil\n\tcase string:\n\t\tvar err error\n\t\td.Duration, err = time.ParseDuration(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unable to parse duration, expected a duration string or a float, but got %T\", v)\n\t}\n}\n\nvar (\n\t_ json.Unmarshaler = (*Duration)(nil)\n\t_ json.Marshaler   = (*Duration)(nil)\n)\n\n// OptionalInteger represents an integer that has a default value\n//\n// When encoded in json, Default is encoded as \"null\".\ntype OptionalInteger struct {\n\tvalue *int64\n}\n\n// NewOptionalInteger returns an OptionalInteger from a int64.\nfunc NewOptionalInteger(v int64) *OptionalInteger {\n\treturn &OptionalInteger{value: &v}","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/config/types.go#L299-L335","documentation":"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.","triggerScenarios":"Unmarshaling a Duration field with a non-string/non-number JSON value, e.g. {\"Timeout\": {\"secs\": 5}} or {\"Timeout\": true}.","commonSituations":"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.","solutions":["Use a duration string: {\"Timeout\": \"1h30m\"}.","Or use a plain number of seconds: {\"Timeout\": 3600}.","Check the producing code so time.Duration-like values are serialized as strings or seconds, not objects."],"exampleFix":"// before\n{\"Provider.Timeout\": {\"secs\": 60, \"nanos\": 0}}\n// after\n{\"Provider.Timeout\": \"60s\"}","handlingStrategy":"validation","validationCode":"func validDuration(v any) error {\n    switch t := v.(type) {\n    case string:\n        _, err := time.ParseDuration(t)\n        return err\n    case float64:\n        return nil // seconds\n    default:\n        return fmt.Errorf(\"duration must be string or seconds number, got %T\", v)\n    }\n}","typeGuard":"func isDurationValue(v any) bool {\n    switch t := v.(type) {\n    case string:\n        _, err := time.ParseDuration(t)\n        return err == nil\n    case float64:\n        return true\n    }\n    return false\n}","tryCatchPattern":"if err := json.Unmarshal(data, &cfg); err != nil {\n    if strings.Contains(err.Error(), \"unable to parse duration\") {\n        // fix the field to \"300ms\"-style string or seconds number\n    }\n}","preventionTips":["Serialize time.Duration as a string (d.String()) or seconds float, never a struct","Avoid YAML->JSON converters that emit durations as maps","Unit-test config marshaling round-trips"],"tags":["config","json-parsing","duration"],"backgroundTag":"invalid-config-value","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}