hyperledger/fabric · error

must be pointer to struct, but got non-pointer %v

Error message

must be pointer to struct, but got non-pointer %v

What it means

SerializableChecks in the lifecycle Serializer requires the input to be a pointer to a struct because it writes each struct field as a separate state key. If reflect.ValueOf(structure).Kind() is not reflect.Pointer, it returns this error reporting the actual kind.

Source

Thrown at core/chaincode/lifecycle/serializer.go:80

// Serializer is used to write structures into the db and to read them back out.
// Although it's unfortunate to write a custom serializer, rather than to use something
// pre-written, like protobuf or JSON, in order to produce precise readwrite sets which
// only perform state updates for keys which are actually updated (and not simply set
// to the same value again) custom serialization is required.
type Serializer struct {
	// Marshaler, when nil uses the standard protobuf impl.
	// Can be overridden for test.
	Marshaler Marshaler
}

// SerializableChecks performs some boilerplate checks to make sure the given structure
// is serializable.  It returns the reflected version of the value and a slice of all
// field names, or an error.
func (s *Serializer) SerializableChecks(structure any) (reflect.Value, []string, error) {
	value := reflect.ValueOf(structure)
	if value.Kind() != reflect.Pointer {
		return reflect.Value{}, nil, errors.Errorf("must be pointer to struct, but got non-pointer %v", value.Kind())
	}

	value = value.Elem()
	if value.Kind() != reflect.Struct {
		return reflect.Value{}, nil, errors.Errorf("must be pointers to struct, but got pointer to %v", value.Kind())
	}

	allFields := make([]string, value.NumField())
	for i := 0; i < value.NumField(); i++ {
		fieldName := value.Type().Field(i).Name
		fieldValue := value.Field(i)
		allFields[i] = fieldName
		switch fieldValue.Kind() {
		case reflect.String:
		case reflect.Int64:
		case reflect.Slice:
			if fieldValue.Type().Elem().Kind() != reflect.Uint8 {
				return reflect.Value{}, nil, errors.Errorf("unsupported slice type %v for field %s", fieldValue.Type().Elem().Kind(), fieldName)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Pass a pointer to the struct: Serialize(state, metadata, &myStruct)
  2. Check the call site type so the argument is T* not T
  3. Wrap the value before calling: obj := MyStruct{...}; s.Serialize(state, metadata, &obj)

Example fix

// before
err := s.Serialize(state, metadata, ChaincodeDefinition{Name: "cc"})
// after
err := s.Serialize(state, metadata, &ChaincodeDefinition{Name: "cc"})
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := any(v).(*MyStruct); !ok {
  return errors.New("Serialize requires *MyStruct, got value type")
}

Type guard

func isStructPointer[T any](v any) bool {
  rv := reflect.ValueOf(v)
  return rv.Kind() == reflect.Pointer && rv.Elem().Kind() == reflect.Struct
}

Try / catch

_, _, err := s.SerializableChecks(input)
if err != nil && strings.Contains(err.Error(), "non-pointer") {
  // wrap in a pointer and retry
}

Prevention

When it happens

Trigger: Calling serializer.Serialize or IsSerialized/IsMetadataSerialized with a struct value (not pointer), a map, or a basic type, e.g. Serialize(state, metadata, MyStruct{...}) instead of &MyStruct{...}.

Common situations: Passing a struct literal by value to Serialize; refactoring code that changed a pointer parameter to a value; deserializing into a non-pointer type in unit tests.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/47371e0df5544848. Report an issue: GitHub.