siyuan-note/siyuan · error
value must not be null
Error message
value must not be null
What it means
decodeRequestValue in kernel/apicontract/decode.go rejects a JSON literal null for any destination that is not a pointer, map, or slice. Pointers/maps/slices accept null (set to zero value), but a plain string, number, or bool field cannot represent null, so the decoder fails fast instead of silently coercing. This keeps typed contract fields strictly typed.
Solutions
- Omit the field entirely instead of sending null
- Send a sensible zero value (empty string, false, 0) rather than null
- Make the field optional in your client by conditionally including it in the payload
- Check the contract type in kernel/apicontract/ to see which fields accept null (pointer/map/slice only)
Example fix
// before
const payload = { id: null, title: "doc" };
// after
const payload = { title: "doc" }; // omit null fields Defensive patterns
Strategy: type-guard
Validate before calling
function stripNulls(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null));
}
payload = stripNulls(payload); Type guard
const isNullableField = (kind) => ["pointer", "map", "slice"].includes(kind); // only these accept null server-side
Try / catch
try { await fetchPost(url, stripNulls(payload)); } catch (e) { if (String(e).includes("must not be null")) console.error("Non-nullable field sent as null:", e); } Prevention
- Omit optional fields instead of sending null
- Use JSON.stringify replacers to drop null values
- Check contract structs to know which fields accept null
- Enable TS strictNullChecks so nulls cannot sneak into payloads
When it happens
Trigger: Posting a JSON body where a non-pointer scalar contract field (e.g. a string id, bool flag, or number) is explicitly set to null, either literally or via JSON.stringify of an object whose property is null; nested through decodeExportFields/decodeRequestFields.
Common situations: JavaScript clients building objects with null placeholders; optional fields represented as null instead of being omitted; older plugin code written against a nullable field that became non-nullable in the contract.
Related errors
- configuration must not be null
- element [ ]
- empty JSON value
- entry [ ]
- Field [conf] should be of type [Object]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/9912a8f0b0eac440.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/decode.go:171
}
}
}
}
return nil
}
// decodeRequestValue 递归绑定复合参数,避免数组元素和嵌套字段绕过空值及必填检查。
func decodeRequestValue(raw json.RawMessage, value reflect.Value) error {
if value.Type() == reflect.TypeFor[JSONValue]() || value.Type() == reflect.TypeFor[PublishDataValue]() {
return json.Unmarshal(raw, value.Addr().Interface())
}
if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
switch value.Kind() {
case reflect.Pointer, reflect.Map, reflect.Slice:
value.SetZero()
return nil
default:
return fmt.Errorf("value must not be null")
}
}
switch value.Kind() {
case reflect.Pointer:
value.Set(reflect.New(value.Type().Elem()))
return decodeRequestValue(raw, value.Elem())
case reflect.Struct:
var fields map[string]json.RawMessage
if err := json.Unmarshal(raw, &fields); err != nil {
return err
}
return decodeRequestFields(value, fields)
case reflect.Slice:
var entries []json.RawMessage
if err := json.Unmarshal(raw, &entries); err != nil {
return err
}
value.Set(reflect.MakeSlice(value.Type(), len(entries), len(entries)))View on GitHub (pinned to 9f775e8a12)