{"record":{"id":"f60fd868fd46d631","repo":"AdguardTeam/AdGuardHome","slug":"json-time-is-nil","errorCode":null,"errorMessage":"json time is nil","messagePattern":"json time is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/aghhttp/json.go","lineNumber":78,"sourceCode":"// type check\nvar _ json.Marshaler = JSONTime{}\n\n// MarshalJSON implements the json.Marshaler interface for JSONTime.  err is\n// always nil.\nfunc (t JSONTime) MarshalJSON() (b []byte, err error) {\n\tmsec := float64(time.Time(t).UnixNano()) / nsecPerMsec\n\tb = strconv.AppendFloat(nil, msec, 'f', -1, 64)\n\n\treturn b, nil\n}\n\n// type check\nvar _ json.Unmarshaler = (*JSONTime)(nil)\n\n// UnmarshalJSON implements the json.Marshaler interface for *JSONTime.\nfunc (t *JSONTime) UnmarshalJSON(b []byte) (err error) {\n\tif t == nil {\n\t\treturn fmt.Errorf(\"json time is nil\")\n\t}\n\n\tmsec, err := strconv.ParseFloat(string(b), 64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"parsing json time: %w\", err)\n\t}\n\n\t*t = JSONTime(time.Unix(0, int64(msec*nsecPerMsec)).UTC())\n\n\treturn nil\n}\n\n// WriteJSONResponse writes headers with the code, encodes resp into w, and logs\n// any errors it encounters.  r is used to get additional information from the\n// request.  l, w, and r must not be nil.\nfunc WriteJSONResponse(\n\tctx context.Context,\n\tl *slog.Logger,","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/aghhttp/json.go#L60-L96","documentation":"The DNS PATCH handler successfully decoded the request and applied the requested fields to a copy of the current DNS config, but confMgr.UpdateDNS rejected the resulting configuration. The wrapped error describes which DNS setting was invalid (upstream servers, bootstrap DNS, listening addresses, etc.).","triggerScenarios":"PATCHing DNS settings where the merged configuration is invalid: malformed upstream DNS URLs, invalid listen addresses, bad bootstrap values, or an invalid upstream mode.","commonSituations":"Typo in an upstream DNS server URL (missing dns:// scheme or port); setting an empty upstream list; combining incompatible DNS options; a partial PATCH interacting badly with existing config values.","solutions":["Read the wrapped error from UpdateDNS — it names the offending field/value","Fix the specific DNS setting (upstream URL format, listen address syntax) and retry the PATCH","GET the current DNS config first, validate the merged result locally, then PATCH","Consult the DNS configuration docs for accepted upstream URL formats"],"exampleFix":"// before\n{\"upstream_dns\": [\"8.8.8.8\"]}\n// after\n{\"upstream_dns\": [\"8.8.8.8:53\"]} // or \"https://dns.google/dns-query\"","handlingStrategy":"validation","validationCode":"// GET current config, merge, validate upstream formats client-side\nif !validUpstreamURL(u) { return fmt.Errorf(\"bad upstream %q\", u) }","typeGuard":"func validUpstreamURL(s string) bool {\n    _, err := url.Parse(s)\n    return err == nil && (strings.Contains(s, \"://\") || net.ParseIP(s) != nil || isHostPort(s))\n}","tryCatchPattern":"if err := updateDNS(cfg); err != nil {\n    if strings.Contains(err.Error(), \"updating:\") {\n        // field-level validation issue; fix named field and retry\n    }\n}","preventionTips":["Fetch and merge current config before PATCHing","Follow documented upstream DNS URL formats","Dry-run validation of DNS settings in the UI first"],"tags":["dns","config-update","http","validation","go"],"backgroundTag":"config-validation-failed","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}