-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Client
Spanner
Environment
macOS (Apple Silicon)
Go 1.24.0
Using Row.ToStruct() with custom types
Code and Dependencies
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/spanner"
)
type A struct {
B *B
}
type B struct {
Val string
}
func (b *B) DecodeSpanner(input interface{}) error {
log.Println("DecodeSpanner called") // ← never called
if s, ok := input.(string); ok {
b.Val = "decoded:" + s
return nil
}
return fmt.Errorf("unexpected type: %T", input)
}
func main() {
row := spanner.RowFromSlice([]any{"Val"}, []any{"foo"})
var a A
if err := row.ToStruct(&a); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", a)
}
Expected behavior
When using Row.ToStruct(), and the destination struct has a field of type *T where *T implements spanner.Decoder, DecodeSpanner() should be invoked and the field should be populated accordingly.
Pointer fields are commonly used in golang projects
Actual behavior
The custom DecodeSpanner() method is not called when the destination field is a pointer.
This appears to be because a **T value is passed down during decoding, which fails the spanner.Decoder interface check.
As a result, the pointer remains nil.
google-cloud-go/spanner/value.go
Line 4436 in 2d66d4f
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface(), opts...); err != nil { |
Screenshots
n/a
Additional context
Related internal code:
google-cloud-go/spanner/value.go
Line 4436 in 2d66d4f
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface(), opts...); err != nil { |
Workarounds considered:
Changing *B to B → loses nullability
Manual decoding with GenericColumnValue → verbose and breaks struct mapping
Please let me know if there's a recommended idiomatic pattern for this case.