{"record":{"id":"dc5efe26c8773213","repo":"siyuan-note/siyuan","slug":"field-conf-is-required","errorCode":null,"errorMessage":"Field [conf] is required","messagePattern":"Field \\[conf\\] is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/graph_input.go","lineNumber":16,"sourceCode":"package apicontract\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\n// GraphConfiguration 延迟绑定具体图配置，使局部图继续忽略全局图字段，并保留默认值合并顺序。\ntype GraphConfiguration struct{ raw json.RawMessage }\n\nfunc (c *GraphConfiguration) UnmarshalJSON(data []byte) error {\n\tif bytes.Equal(bytes.TrimSpace(data), []byte(\"null\")) {\n\t\treturn fmt.Errorf(\"Field [conf] is required\")\n\t}\n\tfields, err := legacyJSONValue[map[string]json.RawMessage](data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Field [conf] should be of type [Object]\")\n\t}\n\tc.raw, err = json.Marshal(fields)\n\treturn err\n}\n\nfunc (c GraphConfiguration) MarshalJSON() ([]byte, error) {\n\tif len(c.raw) == 0 {\n\t\treturn []byte(\"{}\"), nil\n\t}\n\treturn c.raw, nil\n}\n\ntype GraphConfigurationFields struct {\n\tMinRefs   *int             `json:\"minRefs\" api:\"optional\"`","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/graph_input.go#L1-L34","documentation":"GraphConfiguration is a lazy-binding wrapper for graph config; its UnmarshalJSON rejects JSON null because an 'Object' conf is mandatory for graph endpoints. Null would erase defaults/merge order, so it raises 'Field [conf] is required'. If the data is present but not an object, a separate 'should be of type [Object]' error is returned.","triggerScenarios":"Calling a graph API (e.g. getGraph or local graph config) with {\"conf\":null} or a body whose conf key decodes to null.","commonSituations":"Clients clearing graph settings by sending null instead of an empty object; refactored code where conf became optional; serialized config where conf was dropped and null-substituted.","solutions":["Send a JSON object for conf, e.g. {} to accept defaults, instead of null","If resetting settings, pass an empty object rather than null","Check upstream serialization so conf is omitted from the reset path or defaulted to {}"],"exampleFix":"// before\nfetchPost(\"/api/graph/getGraph\", { id: \"nb\", conf: null });\n// after\nfetchPost(\"/api/graph/getGraph\", { id: \"nb\", conf: {} });","handlingStrategy":"validation","validationCode":"if (conf === null || conf === undefined) conf = {};\nif (typeof conf !== \"object\" || Array.isArray(conf)) throw new TypeError(\"conf must be a plain object\");","typeGuard":"const hasConf = (b) => b.conf !== null && b.conf !== undefined && typeof b.conf === \"object\" && !Array.isArray(b.conf);","tryCatchPattern":"try {\n  await fetchPost(\"/api/graph/getGraph\", { id, conf });\n} catch (e) {\n  if (String(e.message).includes(\"[conf] is required\")) {\n    await fetchPost(\"/api/graph/getGraph\", { id, conf: {} });\n  }\n}","preventionTips":["Default null/undefined conf to {} at the call site","Never use null to 'clear' object config fields; send {}","Type conf as a required object in client code"],"tags":["null-value","validation","api-request"],"backgroundTag":"null-argument","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}