gohugoio/hugo · error

cannot unmarshal CSV into %T

Error message

cannot unmarshal CSV into %T

What it means

Thrown by Decoder.unmarshalCSV (parser/metadecoders/decoder.go:377) in the default branch of the type switch when the target 'v' is not *interface{} (*any). The CSV decoder only knows how to populate a generic any (which it then sets to either a [][]string for slice mode or []map[string]string for map mode); passing a typed pointer like *[][]string or *MyStruct hits the default branch. The %T names the unsupported destination type.

Source

Thrown at parser/metadecoders/decoder.go:377

				seen[fieldName] = true
			}

			sm := make([]map[string]string, len(records)-1)
			for i, record := range records[1:] {
				m := make(map[string]string, len(records[0]))
				for j, col := range record {
					m[records[0][j]] = col
				}
				sm[i] = m
			}
			*vv = sm
		case "slice":
			*vv = records
		default:
			return fmt.Errorf("cannot unmarshal CSV into %T: invalid targetType: expected either slice or map, received %s", v, d.TargetType)
		}
	default:
		return fmt.Errorf("cannot unmarshal CSV into %T", v)
	}

	return nil
}

func parseORGDate(s string) string {
	r := regexp.MustCompile(`[<\[](\d{4}-\d{2}-\d{2}) .*[>\]]`)
	if m := r.FindStringSubmatch(s); m != nil {
		return m[1]
	}
	return s
}

func (d Decoder) unmarshalORG(data []byte, v any) error {
	config := org.New()
	config.Log = log.Default() // TODO(bep)
	document := config.Parse(bytes.NewReader(data), "")
	if document.Error != nil {

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Decode CSV into a *interface{} (var v any; dec.UnmarshalTo(data, metadecoders.CSV, &v)) then type-assert the result.
  2. For slice mode the result is [][]string; for map mode it is []map[string]string — assert accordingly.
  3. Use encoding/csv directly if you need a typed reader.

Example fix

// before
var rows [][]string
err := dec.UnmarshalTo(data, metadecoders.CSV, &rows)

// after
var v any
err := dec.UnmarshalTo(data, metadecoders.CSV, &v)
rows := v.([][]string)
Defensive patterns

Strategy: type-guard

Validate before calling

// CSV decoder only accepts *interface{}.
func isAnyPointer(v any) bool {
    _, ok := v.(*any)
    return ok
}

Type guard

func isCSVTarget(v any) bool {
    _, ok := v.(*any)
    return ok
}

Try / catch

var v any
if err := dec.UnmarshalTo(data, metadecoders.CSV, &v); err != nil {
    return fmt.Errorf("CSV decode: %w", err)
}
// type-assert: v.([][]string) or v.([]map[string]string)

Prevention

When it happens

Trigger: Calling UnmarshalTo/Unmarshal for CSV with v = &rows where rows is [][]string, or v = &myStruct; programmatic misuse expecting reflection-based typed decoding that the CSV path does not support.

Common situations: Theme/plugin authors calling the metadecoders CSV path with a concrete typed destination; assuming CSV decodes into typed structs like JSON/TOML do.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/0fed5c1893f4cc36. Report an issue: GitHub.