package main import ( "encoding/json" "flag" "fmt" "io" "os" "github.com/ryeguard/ddbcalc" ) var fileFlag = flag.String("file", "", "The file path and name to read. Must be a json file. Required.") func readJSON(file string) (map[string]interface{}, error) { f, err := os.Open(file) if err != nil { return nil, fmt.Errorf("open: %w", err) } defer f.Close() bytes, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf("read: %w", err) } var data map[string]interface{} err = json.Unmarshal(bytes, &data) if err != nil { return nil, fmt.Errorf("unmarshal: %w", err) } return data, nil } func main() { flag.Parse() if *fileFlag == "" { flag.PrintDefaults() os.Exit(1) } data, err := readJSON(*fileFlag) if err != nil { panic(fmt.Sprintf("readJSON: %v", err)) } size, err := ddbcalc.StructSizeInBytes(data) if err != nil { panic(fmt.Sprintf("StructSizeInBytes: %v", err)) } fmt.Printf("The resulting DynamoDB item size is %d bytes\n", size) }
package ddbcalc import ( "fmt" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/ryeguard/ddbcalc/internal/calc" ) const SizeLimitInBytes = 400_000 // 400 KB // MapSizeInBytes returns the size of a map of AttributeValue in bytes. func MapSizeInBytes(m map[string]types.AttributeValue) (int, error) { size := 0 for k, v := range m { size += len([]byte(k)) size += calc.SizeInBytes(&v) } return size, nil } // StructSizeInBytes returns the size of a struct in bytes. // It is a convenience function as an alternative to first calling attributevalue.MarshalMap and then MapSizeInBytes. func StructSizeInBytes(s interface{}) (int, error) { av, err := attributevalue.MarshalMap(s) if err != nil { return 0, fmt.Errorf("marshal map: %w", err) } return MapSizeInBytes(av) }
package calc import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) const ( overheadMemberM = 3 // 3 byte overheadMemberL = 3 // 3 byte overheadElement = 1 // 1 byte ) func SizeInBytes(av *types.AttributeValue) int { if av == nil { return 0 } switch _av := (*av).(type) { case *types.AttributeValueMemberS: return len(_av.Value) case *types.AttributeValueMemberN: return len(_av.Value) case *types.AttributeValueMemberB: return len(_av.Value) case *types.AttributeValueMemberBOOL: return 1 case *types.AttributeValueMemberNULL: return 1 case *types.AttributeValueMemberBS: size := 0 for _, v := range _av.Value { size += len(v) } return size case *types.AttributeValueMemberL: return listSize(_av) case *types.AttributeValueMemberM: return mapSize(_av) case *types.AttributeValueMemberNS: size := 0 for _, v := range _av.Value { size += len(v) } return size case *types.AttributeValueMemberSS: size := 0 for _, v := range _av.Value { size += len(v) } return size default: return 0 } } func listSize(l *types.AttributeValueMemberL) int { size := overheadMemberL for _, v := range l.Value { size += SizeInBytes(&v) size += overheadElement } return size } func mapSize(m *types.AttributeValueMemberM) int { size := overheadMemberM for k, v := range m.Value { size += len(k) size += SizeInBytes(&v) size += overheadElement } return size }