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
}