Tencent/WeKnora · error

marshal optionalPayload: %w

Error message

marshal optionalPayload: %w

What it means

submitJob marshals the converter's optionalPayload (extra model options sent as the optionalPayload form field) with encoding/json before building the multipart request. Since the payload is composed of plain Go values, this should never fail in practice; if it does, it signals a payload containing a value json.Marshal cannot encode (e.g. a channel, func, or cyclic structure) introduced by a code change.

Source

Thrown at internal/infrastructure/docparser/paddleocr_vl_cloud_converter.go:113

func (c *PaddleOCRVLCloudReader) optionalPayload() map[string]interface{} {
	// Shared with the self-hosted engine so both produce identical output.
	return paddleOCRVLRecognitionParams(c.useSeal, c.useChart)
}

// --- job submit ---

type paddleOCRVLCloudSubmitResponse struct {
	Data struct {
		JobID string `json:"jobId"`
	} `json:"data"`
	ErrorCode int    `json:"errorCode"`
	ErrorMsg  string `json:"errorMsg"`
}

func (c *PaddleOCRVLCloudReader) submitJob(ctx context.Context, req *types.ReadRequest, content []byte) (string, error) {
	optional, err := json.Marshal(c.optionalPayload())
	if err != nil {
		return "", fmt.Errorf("marshal optionalPayload: %w", err)
	}

	fileName := req.FileName
	if fileName == "" {
		ext := strings.TrimPrefix(req.FileType, ".")
		if ext == "" {
			ext = "pdf"
		}
		fileName = "document." + ext
	}

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	_ = writer.WriteField("model", c.model)
	_ = writer.WriteField("optionalPayload", string(optional))
	part, err := writer.CreateFormFile("file", filepath.Base(fileName))
	if err != nil {
		return "", fmt.Errorf("create form file: %w", err)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Check the wrapped error for 'json: unsupported type: ...' and fix the offending field type
  2. Ensure all optionalPayload fields are JSON-encodable primitives, slices, maps, or tagged structs
  3. Add a unit test marshaling optionalPayload to catch regressions
  4. If a dynamic option must carry non-encodable data, convert it to a string/encodable representation first

Example fix

// before
Options map[string]any // may hold unsupported values
// after
func (c *PaddleOCRVLCloudReader) optionalPayload() map[string]any {
	return map[string]any{"model": c.model, "language": c.language} // encodable fields only
}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := json.Marshal(c.optionalPayload()); err != nil {
    return fmt.Errorf("optionalPayload not encodable: %w", err)
}

Type guard

func optionalPayloadEncodable(p map[string]any) bool {
    _, err := json.Marshal(p)
    return err == nil
}

Try / catch

out, err := reader.Read(ctx, req)
if err != nil && strings.Contains(err.Error(), "marshal optionalPayload") {
    // developer bug: fix the payload type, not a runtime retry
    return fmt.Errorf("config bug in optionalPayload: %w", err)
}

Prevention

When it happens

Trigger: Read -> submitJob -> json.Marshal(c.optionalPayload()) returns an error — only possible when optionalPayload is extended with unsupported types (chan, func, unencodable cyclic data).

Common situations: A recent code change added a custom option value (e.g. a struct with a func or chan field) to optionalPayload; unsupported-type JSON fields added without json tags or marshaling support.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/1cbf66456df0c010. Report an issue: GitHub.