gofiber/fiber · error

sse: marshal data

Error message

sse: marshal data: %w

What it means

Thrown by eventData when the Event.Data value is not one of the pass-through types (nil, string, []byte, json.RawMessage) and the configured JSON marshaler fails to encode it. The %w wraps the json.Marshal (or custom JSONMarshal) error. Data values of these special types bypass JSON; everything else is JSON-encoded, so a non-JSON-encodable value triggers this.

Solutions

  1. Only pass JSON-encodable values as Event.Data.
  2. For raw/preformatted payloads, pass a string or []byte (these bypass JSON entirely).
  3. If you use a custom jsonMarshal (via app config or Stream.Event variadic), ensure it handles your types or returns a clear error.
  4. Implement json.Marshaler correctly on custom types; test MarshalJSON separately.

Example fix

// before
stream.Event(sse.Event{Data: struct{ Cb func() }{}})
// -> sse: marshal data: json: unsupported type: func()

// after — pass a serializable DTO
stream.Event(sse.Event{Data: MyDTO{ID: 1, Name: "x"}})
// or send raw preformatted JSON
stream.Event(sse.Event{Data: []byte(`{"id":1}`)})
Defensive patterns

Strategy: validation

Validate before calling

// Pre-marshal to fail fast with a clear error before hitting the stream.
func mustSSEData(v any) any {
    switch v.(type) {
    case nil, string, []byte, json.RawMessage:
        return v
    default:
        if _, err := json.Marshal(v); err != nil {
            log.Fatalf("Event.Data not JSON-encodable: %v", err)
        }
        return v
    }
}

Type guard

func isJSONEncodable(v any) bool {
    switch v.(type) {
    case nil, string, []byte, json.RawMessage:
        return true
    }
    _, err := json.Marshal(v)
    return err == nil
}

Try / catch

if !isJSONEncodable(ev.Data) {
    ev.Data = []byte(`{"error":"non-serializable payload"}`)
}
if err := stream.Event(ev); err != nil { return }

Prevention

When it happens

Trigger: Passing Event.Data as a func, channel, complex64/128, a struct with unexported fields of unsupported types, a value implementing json.Marshaler whose MarshalJSON returns an error, or a cyclic structure.

Common situations: Streaming a struct that contains a non-serializable field (e.g. a sync.Mutex, a *sql.DB, a func callback); passing a map with non-string keys; a custom MarshalJSON that errors on edge cases.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/2555eb3023c151a7. Report an issue: GitHub.

Appendix: source

Thrown at middleware/sse/event.go:109

type eventPayload struct {
	data    string
	hasData bool
}

func eventData(data any, jsonMarshal utils.JSONMarshal) (eventPayload, error) {
	switch value := data.(type) {
	case nil:
		return eventPayload{}, nil
	case string:
		return eventPayload{data: value, hasData: true}, nil
	case []byte:
		return eventPayload{data: string(value), hasData: true}, nil
	case json.RawMessage:
		return eventPayload{data: string(value), hasData: true}, nil
	default:
		encoded, err := jsonMarshal(value)
		if err != nil {
			return eventPayload{}, fmt.Errorf("sse: marshal data: %w", err)
		}
		return eventPayload{data: string(encoded), hasData: true}, nil
	}
}

func jsonMarshalOrDefault(jsonMarshal []utils.JSONMarshal) utils.JSONMarshal {
	if len(jsonMarshal) > 0 && jsonMarshal[0] != nil {
		return jsonMarshal[0]
	}
	return json.Marshal
}

func appendField(w *bytes.Buffer, field, value string) {
	w.WriteString(field) //nolint:errcheck // bytes.Buffer writes never fail.
	w.WriteString(": ")  //nolint:errcheck // bytes.Buffer writes never fail.
	w.WriteString(value) //nolint:errcheck // bytes.Buffer writes never fail.
	w.WriteByte('\n')    //nolint:errcheck // bytes.Buffer writes never fail.
}

View on GitHub (pinned to a105acad6c)