github.com/MetalBlockchain/metalgo@v1.11.9/codec/reflectcodec/struct_fielder.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package reflectcodec 5 6 import ( 7 "fmt" 8 "reflect" 9 "sync" 10 11 "github.com/MetalBlockchain/metalgo/codec" 12 ) 13 14 // TagValue is the value the tag must have to be serialized. 15 const TagValue = "true" 16 17 var _ StructFielder = (*structFielder)(nil) 18 19 // StructFielder handles discovery of serializable fields in a struct. 20 type StructFielder interface { 21 // Returns the fields that have been marked as serializable in [t], which is 22 // a struct type. 23 // Returns an error if a field has tag "[tagName]: [TagValue]" but the field 24 // is un-exported. 25 // GetSerializedField(Foo) --> [1,5,8] means Foo.Field(1), Foo.Field(5), 26 // Foo.Field(8) are to be serialized/deserialized. 27 GetSerializedFields(t reflect.Type) ([]int, error) 28 } 29 30 func NewStructFielder(tagNames []string) StructFielder { 31 return &structFielder{ 32 tags: tagNames, 33 serializedFieldIndices: make(map[reflect.Type][]int), 34 } 35 } 36 37 type structFielder struct { 38 lock sync.RWMutex 39 40 // multiple tags per field can be specified. A field is serialized/deserialized 41 // if it has at least one of the specified tags. 42 tags []string 43 44 // Key: a struct type 45 // Value: Slice where each element is index in the struct type of a field 46 // that is serialized/deserialized e.g. Foo --> [1,5,8] means Foo.Field(1), 47 // etc. are to be serialized/deserialized. We assume this cache is pretty 48 // small (a few hundred keys at most) and doesn't take up much memory. 49 serializedFieldIndices map[reflect.Type][]int 50 } 51 52 func (s *structFielder) GetSerializedFields(t reflect.Type) ([]int, error) { 53 if serializedFields, ok := s.getCachedSerializedFields(t); ok { // use pre-computed result 54 return serializedFields, nil 55 } 56 57 s.lock.Lock() 58 defer s.lock.Unlock() 59 60 numFields := t.NumField() 61 serializedFields := make([]int, 0, numFields) 62 for i := 0; i < numFields; i++ { // Go through all fields of this struct 63 field := t.Field(i) 64 65 // Multiple tags per fields can be specified. 66 // Serialize/Deserialize field if it has 67 // any tag with the right value 68 var captureField bool 69 for _, tag := range s.tags { 70 if field.Tag.Get(tag) == TagValue { 71 captureField = true 72 break 73 } 74 } 75 if !captureField { 76 continue 77 } 78 if !field.IsExported() { // Can only marshal exported fields 79 return nil, fmt.Errorf("can not marshal %w: %s", 80 codec.ErrUnexportedField, 81 field.Name, 82 ) 83 } 84 serializedFields = append(serializedFields, i) 85 } 86 s.serializedFieldIndices[t] = serializedFields // cache result 87 return serializedFields, nil 88 } 89 90 func (s *structFielder) getCachedSerializedFields(t reflect.Type) ([]int, bool) { 91 s.lock.RLock() 92 defer s.lock.RUnlock() 93 94 cachedFields, ok := s.serializedFieldIndices[t] 95 return cachedFields, ok 96 }