ory/hydra · error

expected JSON value to be an array but got type: %s

Error message

expected JSON value to be an array but got type: %s

What it means

After decoding the scanned text, StringSliceJSONFormat.Scan checks that the JSON value is actually an array before unmarshalling into []string. If the stored document is an object, string, number, or boolean, it returns this error with the gjson type name. The database column contains valid JSON but of the wrong shape for a string slice.

Source

Thrown at oryx/sqlxx/types.go:70

	switch v := value.(type) {
	case nil:
		*m = StringSliceJSONFormat{}
		return nil
	case string:
		val = v
	case []byte:
		val = string(v)
	default:
		return errors.Errorf("cannot scan %#v into StringSliceJSONFormat", value)
	}
	if len(val) == 0 {
		val = "[]"
	}

	if parsed := gjson.Parse(val); parsed.Type == gjson.Null {
		val = "[]"
	} else if !parsed.IsArray() {
		return errors.Errorf("expected JSON value to be an array but got type: %s", parsed.Type.String())
	}

	return errors.WithStack(json.Unmarshal([]byte(val), &m))
}

// Value implements the driver Valuer interface.
func (m StringSliceJSONFormat) Value() (driver.Value, error) {
	if len(m) == 0 {
		return "[]", nil
	}

	encoded, err := json.Marshal(&m)
	return string(encoded), errors.WithStack(err)
}

// StringSlicePipeDelimiter de/encodes the string slice to/from a SQL string.
type StringSlicePipeDelimiter []string

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Fix the writer so it marshals []string (ensuring `json.Marshal` is called on a slice, producing `["a","b"]`).
  2. Migrate existing bad rows: UPDATE t SET col = ... to convert objects/scalars into arrays (e.g. wrap scalars: jsonb_build_array(col)).
  3. If the data is genuinely an object, change the model to a matching type (e.g. a map/string-map type) instead of StringSliceJSONFormat.
  4. Add a read-side normalization step that converts legacy shapes to arrays before assignment.

Example fix

// before
meta := map[string]string{"k": "v"}
db.Exec("UPDATE items SET tags = ?", mustJSON(meta)) // writes object -> Scan fails

// after
tags := []string{"a", "b"}
db.Exec("UPDATE items SET tags = ?", mustJSON(tags)) // writes ["a","b"]
Defensive patterns

Strategy: validation

Validate before calling

// validate stored value is a JSON array before scanning
doc, _ := io.ReadAll(rows)
if gjson.Parse(string(doc)).Type != gjson.Null && !gjson.Parse(string(doc)).IsArray() {
    return fmt.Errorf("expected JSON array in column, got: %s", string(doc))
}

Type guard

func isJSONArrayDoc(s string) bool {
    p := gjson.Parse(s)
    return p.Type == gjson.Null || p.IsArray()
}

Try / catch

var tags sqlxx.StringSliceJSONFormat
if err := rows.Scan(&tags); err != nil {
    if strings.Contains(err.Error(), "expected JSON value to be an array") {
        // legacy/object-shaped row: normalize or surface a data-migration error
        return fmt.Errorf("row has non-array JSON in tags column: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Scanning a text/JSON column whose content is `{...}` (object), `"text"`, `123`, or `true` into a *StringSliceJSONFormat; an application bug writing a JSON object where an array was expected; legacy rows written by an older schema version using a different shape.

Common situations: Mixed-version writers: new code writes an object, old reader expects an array; hand-edited DB rows; importing data from another system where the field was a map; a single-value column that stores `"foo"` (JSON string) instead of `["foo"]`.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/a80813ff86458032. Report an issue: GitHub.