semaphoreui/semaphore · error

expected string for field, got %T

Error message

expected string for field, got %T

What it means

In setBasicType (services/project/backup_marshal.go:125), when the destination field kind is String, the incoming data must be a JSON string. Any other Go type (number, bool, object, null) yields "expected string for field, got %T". This guards reflect.SetString against invalid types.

Solutions

  1. Quote the value in the backup JSON so it is a proper string (e.g. 123 -> "123").
  2. Change the struct field to a numeric type if the data is legitimately a number.
  3. Pre-validate the backup file's field types against the expected schema before restoring.
  4. Re-export the backup using a version whose schema matches the importing code.

Example fix

// before (backup.json)
"project_id": 5
// after (field is a string type)
"project_id": "5"
Defensive patterns

Strategy: validation

Validate before calling

if v, ok := raw["name"]; ok {
    if _, ok := v.(string); !ok { return fmt.Errorf("field name must be string, got %T", v) }
}

Type guard

func isString(v any) bool { _, ok := v.(string); return ok }

Try / catch

if err := project.Unmarshal(data, &target); err != nil {
    if strings.Contains(err.Error(), "expected string for field") {
        return fmt.Errorf("backup schema mismatch: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: unmarshalValueWithBackupTags maps a backup JSON value of non-string type onto a struct field whose kind is String - e.g. a number where a string is expected.

Common situations: Backup JSON where a string field (e.g. an ID stored as string) was serialized as a number; hand-edited backups; schema drift between the exporting and importing versions where a field changed type from number to string.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/03aeb806ba7c7a57. Report an issue: GitHub.

Appendix: source

Thrown at services/project/backup_marshal.go:125

	return v.Interface(), nil
}

func setBasicType(data any, v reflect.Value) error {
	if !v.CanSet() {
		return fmt.Errorf("cannot set value")
	}

	switch v.Kind() {
	case reflect.Bool:
		b, ok := data.(bool)
		if !ok {
			return fmt.Errorf("expected bool for field, got %T", data)
		}
		v.SetBool(b)
	case reflect.String:
		s, ok := data.(string)
		if !ok {
			return fmt.Errorf("expected string for field, got %T", data)
		}
		v.SetString(s)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)
		}
		v.SetInt(int64(n))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)
		}
		v.SetUint(uint64(n))
	case reflect.Float32, reflect.Float64:
		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)

View on GitHub (pinned to 1774ccb71a)