go-redis/redis · error

redis: JSON.SET mode must be NX or XX

Error message

redis: JSON.SET mode must be NX or XX

What it means

JSONSetWithArgs panics when options.Mode is a non-empty string other than 'NX' or 'XX' (matched case-insensitively after strings.ToUpper). JSON.SET only accepts NX (set if missing) or XX (set if exists) as its mode flag, so any other value is a programmer error and is rejected up front rather than sent to the server.

Solutions

  1. Pass only "NX" or "XX" (any case) as the mode, or empty string to omit the flag.
  2. Validate user-supplied mode against {"", "NX", "XX"} before calling JSONSetWithArgs.
  3. Use JSONSet (no mode) when no conditional flag is needed.

Example fix

// before
client.JSONSetWithArgs(ctx, key, "$", v, &redis.JSONSetArgsOptions{Mode: "EX"})

// after
client.JSONSetWithArgs(ctx, key, "$", v, &redis.JSONSetArgsOptions{Mode: "NX"})
Defensive patterns

Strategy: validation

Validate before calling

func validJSONSetMode(mode string) error {
    switch strings.ToUpper(mode) {
    case "", "NX", "XX":
        return nil
    default:
        return fmt.Errorf("JSON.SET mode must be NX or XX, got %q", mode)
    }
}

Type guard

func isValidJSONSetMode(mode string) bool {
    switch strings.ToUpper(mode) {
    case "", "NX", "XX":
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Calling JSONSetMode/JSONSetWithArgs with mode = "SET", "EX", "XXNX", or any string that is not NX/XX.

Common situations: Confusing JSON.SET mode with SET option flags (EX/PX/EXAT), passing a lower-level SET mode, or a typo like "NX " with trailing whitespace that is not stripped.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /api/errors/683f5ba50aacbf99. Report an issue: GitHub.

Appendix: source

Thrown at json.go:648

func (c cmdable) JSONSetWithArgs(ctx context.Context, key, path string, value interface{}, options *JSONSetArgsOptions) *StatusCmd {
	var bytes []byte
	var err error
	switch v := value.(type) {
	case string:
		bytes = []byte(v)
	case []byte:
		bytes = v
	default:
		bytes, err = json.Marshal(v)
	}
	args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)}
	if options != nil {
		if options.Mode != "" {
			switch strings.ToUpper(options.Mode) {
			case "XX", "NX":
				args = append(args, strings.ToUpper(options.Mode))
			default:
				panic("redis: JSON.SET mode must be NX or XX")
			}
		}
		if options.FPHA != "" {
			args = append(args, "FPHA", string(options.FPHA))
		}
	}
	cmd := NewStatusCmd(ctx, args...)
	if err != nil {
		cmd.SetErr(err)
	} else {
		_ = c(ctx, cmd)
	}
	return cmd
}

// JSONStrAppend appends the JSON-string values to the string at the specified path.
// For more information, see https://redis.io/commands/json.strappend
func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd {

View on GitHub (pinned to 36d97525cd)