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
- Pass a pointer to the struct: Serialize(state, metadata, &myStruct)
- Check the call site type so the argument is T* not T
- 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
- Always pass &Struct{...} to Serialize/Deserialize
- Make serialization helpers take typed pointer parameters so the compiler enforces it
- Add unit tests calling SerializableChecks with production structs
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
- must be pointers to struct, but got pointer to %v
- proto: Marshal called with nil
- unsupported slice type %v for field %s
- unsupported pointer type %v for field %s (must be proto)
- unsupported structure field kind %v for serialization for fi
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/47371e0df5544848.
Report an issue: GitHub.