{"record":{"id":"ab679e8f81dad770","repo":"siyuan-note/siyuan","slug":"missing-jsonrpc-field","errorCode":null,"errorMessage":"missing jsonrpc field","messagePattern":"missing jsonrpc field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/rpc.go","lineNumber":111,"sourceCode":"}\n\nfunc (r *JsonRpcRequest) UnmarshalJSON(data []byte) error {\n\tdecoder := json.NewDecoder(bytes.NewReader(data))\n\t// decoder.DisallowUnknownFields() // Reject unknown fields violates the JSON-RPC spec\n\ttype JsonRpcRequestObject struct {\n\t\tJsonRpc util.Optional[string] `json:\"jsonrpc\"`\n\t\tMethod  util.Optional[string] `json:\"method\"`\n\t\tParams  util.Optional[any]    `json:\"params\"`\n\t\tID      util.Optional[any]    `json:\"id\"`\n\t}\n\trequest := JsonRpcRequestObject{}\n\tif err := decoder.Decode(&request); err != nil {\n\t\treturn err\n\t}\n\n\t// Validate jsonrpc field\n\tif !request.JsonRpc.Exists {\n\t\treturn fmt.Errorf(\"missing jsonrpc field\")\n\t}\n\tif request.JsonRpc.Value != JsonRpcVersion {\n\t\treturn fmt.Errorf(\"invalid jsonrpc version: %s\", request.JsonRpc.Value)\n\t}\n\n\t// Validate method field\n\tif !request.Method.HasValue() {\n\t\treturn fmt.Errorf(\"missing method field\")\n\t}\n\n\t// Validate id field\n\tif !request.ID.Exists {\n\t} else if request.ID.IsNull {\n\t} else if _, ok := request.ID.Value.(string); ok {\n\t} else if _, ok := request.ID.Value.(float64); ok {\n\t} else {\n\t\treturn fmt.Errorf(\"invalid id field: must be string, number, null or omitted\")\n\t}","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/rpc.go#L93-L129","documentation":"Returned by JsonRpcRequest.UnmarshalJSON when the decoded JSON object has no 'jsonrpc' field. The kernel uses util.Optional to distinguish absent vs null, and an absent jsonrpc field means the message is not a valid JSON-RPC 2.0 request. This fires before version comparison.","triggerScenarios":"A plugin's RPC handler receives a JSON body like {\"method\":\"foo\",\"params\":[]} with no jsonrpc field, or a malformed/empty object.","commonSituations":"Client sends a raw method call without the JSON-RPC envelope, or a proxy strips unknown fields, or a hand-built fetch payload forgot the jsonrpc:'2.0'.","solutions":["Include \"jsonrpc\":\"2.0\" in every request body.","Build requests with a shared helper that always adds jsonrpc, method, and id.","Validate the envelope with JSON.parse + field check before sending."],"exampleFix":"// before\nfetch(url, { method: 'POST', body: JSON.stringify({ method: 'doStuff', params: [] }) });\n\n// after\nfetch(url, { method: 'POST', body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'doStuff', params: [] }) });","handlingStrategy":"validation","validationCode":"function hasJsonRpc(o: any): boolean { return !!o && typeof o === 'object' && 'jsonrpc' in o; }","typeGuard":"function isJsonRpcEnvelope(o: unknown): o is { jsonrpc: '2.0'; method: string; params?: unknown; id?: string | number | null } { return !!o && typeof o === 'object' && (o as any).jsonrpc === '2.0' && typeof (o as any).method === 'string'; }","tryCatchPattern":null,"preventionTips":["Always include jsonrpc:'2.0' in every request.","Use a shared rpcCall(id, method, params) helper that adds the envelope.","Validate the envelope before sending."],"tags":["plugin","rpc","jsonrpc","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}