siyuan-note/siyuan · error

empty JSON value

Error message

empty JSON value

What it means

normalizeTransactionJSON validates each entry of the transactions array before binding it to the transaction struct. An entry that is empty (zero-length or whitespace-only after trimming) cannot be interpreted as a JSON value and is rejected with this error, preserving the entry-first numeric-read semantics.

Solutions

  1. Remove empty/whitespace entries from the transactions array before sending
  2. Make each array element a complete JSON object such as {"doOperations":[...],"undoOperations":[...]}
  3. Audit the code that builds the array so it never appends undefined/empty items
  4. Validate the payload client-side with a check that every element parses as JSON

Example fix

// before
transactions: ["", JSON.stringify(tx)]
// after
transactions: [JSON.stringify(tx)]
Defensive patterns

Strategy: validation

Validate before calling

transactions = transactions.filter(t => t != null && JSON.stringify(t).trim() !== "");

Prevention

When it happens

Trigger: A transactions array containing "" or " " string entries that get passed through as raw bytes; slicing/serialization bugs producing empty RawMessage elements; corrupted request bodies from proxies.

Common situations: Custom clients constructing transactions arrays from joined/serialized fragments where an empty fragment slips in; template-generated payloads with unfilled placeholders; JSON round-trip bugs dropping element content.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/a9ca26414f1a9982. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/transaction_decode.go:58

	}
	if request.App, err = legacyField[string](fields, "app", "String", false); err != nil {
		return request, err
	}
	if request.Session, err = legacyField[string](fields, "session", "String", false); err != nil {
		return request, err
	}
	request.TransactionJSON, request.DecodeError = normalizeTransactionJSON(fields["transactions"])
	if request.DecodeError == nil {
		request.DecodeError = json.Unmarshal(request.TransactionJSON, &request.Transactions)
	}
	return request, nil
}

// normalizeTransactionJSON 保留入口先按 JSON 数字读取、再绑定事务结构的数值语义。
func normalizeTransactionJSON(data []byte) ([]byte, error) {
	data = bytes.TrimSpace(data)
	if len(data) == 0 {
		return nil, fmt.Errorf("empty JSON value")
	}
	switch data[0] {
	case '{':
		var fields map[string]json.RawMessage
		if err := json.Unmarshal(data, &fields); err != nil {
			return nil, err
		}
		for key, value := range fields {
			normalized, err := normalizeTransactionJSON(value)
			if err != nil {
				return nil, err
			}
			fields[key] = normalized
		}
		return json.Marshal(fields)
	case '[':
		var items []json.RawMessage
		if err := json.Unmarshal(data, &items); err != nil {

View on GitHub (pinned to 9f775e8a12)