semaphoreui/semaphore · error

expected number for field, got %T

Error message

expected number for field, got %T

What it means

In setBasicType (services/project/backup_marshal.go:131), for signed integer fields (Int..Int64), the data is run through toFloat64; if the value is not a JSON number (or numeric type), the function returns "expected number for field, got %T". toFloat64 only accepts float/int/uint variants, so strings, bools, nil, and objects all fail.

Solutions

  1. Edit the backup JSON so the field is an unquoted JSON number ("42" -> 42).
  2. If the source data is a string of digits, convert it in the exporter or change the struct field to string.
  3. Handle null explicitly: replace null with a numeric default (e.g. 0) in the backup, or make the field a pointer (*int) so nil data is handled upstream.
  4. Re-export the backup with a matching tool version.

Example fix

// before (backup.json)
"build_number": "42"
// after
"build_number": 42
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

func isJSONNumber(v any) bool { switch v.(type) { case float64, float32, int, int64: return true }; return false }

Try / catch

if err := project.Unmarshal(data, &target); err != nil {
    if strings.Contains(err.Error(), "expected number for field") {
        return fmt.Errorf("numeric field has wrong type in backup: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: unmarshalValueWithBackupTags encounters an int-kind struct field whose backup JSON value is a string (e.g. "42"), boolean, null, or nested object.

Common situations: Backups where numeric fields were serialized as strings by a producer tool or hand-editing; null values where an int was expected; schema changes turning a string field into int between versions.

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/36857450227ef2f0. Report an issue: GitHub.

Appendix: source

Thrown at services/project/backup_marshal.go:131

	}

	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)
		}
		v.SetFloat(n)
	default:
		return fmt.Errorf("unsupported kind %v", v.Kind())
	}
	return nil

View on GitHub (pinned to 1774ccb71a)