googleapis/mcp-toolbox · error

failed to unmarshal operation JSON to map

Error message

failed to unmarshal operation JSON to map: %w

What it means

After GetOperation successfully marshals the operation with protojson, it decodes the JSON bytes into a map[string]any for the caller. If the JSON produced cannot be unmarshaled into a map (corrupt or non-object payload), this wrapped error is returned. Practically unreachable with valid protojson output, but it hardens the conversion path.

Solutions

  1. Inspect the wrapped error from json.Unmarshal for the exact cause
  2. Re-run with a stock protojson.Marshal to confirm output is a JSON object
  3. Ensure no middleware rewrites the marshaled bytes
  4. Update the dataplex client/proto dependencies if a known bug applies
Defensive patterns

Strategy: try-catch

Validate before calling

if len(bytes) == 0 || bytes[0] != '{' { return errors.New("operation payload is not a JSON object") }

Try / catch

opData, err := src.GetOperation(ctx, name)
if err != nil {
	if strings.Contains(err.Error(), "unmarshal operation JSON") {
		// fall back to raw operation proto inspection
	}
	return err
}

Prevention

When it happens

Trigger: Calling GetOperation when the intermediate JSON bytes from protojson.Marshal are not a valid JSON object (only possible with corrupted output or replaced marshaler behavior).

Common situations: Custom/patched protojson behavior; intermediary mutation of bytes; extremely rare in production — mostly seen in mocked or instrumented clients.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/b5ceee072c7a15d6. Report an issue: GitHub.

Appendix: source

Thrown at internal/sources/dataplex/dataplex.go:927

		return nil, fmt.Errorf("invalid operation name format: %q (expected projects/*/locations/*/operations/*)", opName)
	}

	req := &longrunningpb.GetOperationRequest{
		Name: opName,
	}
	op, err := s.DataScanClient.LROClient.GetOperation(ctx, req)
	if err != nil {
		return nil, err
	}

	bytes, err := protojson.Marshal(op)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal operation to JSON: %w", err)
	}

	var opData map[string]any
	if err := json.Unmarshal(bytes, &opData); err != nil {
		return nil, fmt.Errorf("failed to unmarshal operation JSON to map: %w", err)
	}

	return opData, nil
}

func (s *Source) GetJobStatus(ctx context.Context, location, scanID, jobID string) (*dataplexpb.DataScanJob, error) {
	// If jobID is provided, fetch that specific job directly!
	if jobID != "" {
		name := fmt.Sprintf("projects/%s/locations/%s/dataScans/%s/jobs/%s", s.ProjectID(), location, scanID, jobID)
		req := &dataplexpb.GetDataScanJobRequest{
			Name: name,
		}
		return s.DataScanClient.GetDataScanJob(ctx, req)
	}

	// Fallback to listing and returning the latest job (PageSize: 1)
	parent := fmt.Sprintf("projects/%s/locations/%s/dataScans/%s", s.ProjectID(), location, scanID)
	req := &dataplexpb.ListDataScanJobsRequest{

View on GitHub (pinned to 8cc6e09de2)