Skip to content

Commit

Permalink
Fix for aws#2920
Browse files Browse the repository at this point in the history
  • Loading branch information
badu committed Jan 29, 2025
1 parent 4ab652d commit 0a9b423
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 0 deletions.
116 changes: 116 additions & 0 deletions feature/dynamodb/attributevalue/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package attributevalue

import (
"encoding"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"strconv"
Expand Down Expand Up @@ -1049,3 +1051,117 @@ func defaultDecodeTimeS(v string) (time.Time, error) {
func defaultDecodeTimeN(v string) (time.Time, error) {
return decodeUnixTime(v)
}

func parseAttributeValue(raw map[string]json.RawMessage) (types.AttributeValue, error) {
for k, v := range raw {
switch k {
case "SS":
var ss []string
if err := json.Unmarshal(v, &ss); err != nil {
return nil, err
}
return &types.AttributeValueMemberSS{Value: ss}, nil
case "NS":
var ns []string
if err := json.Unmarshal(v, &ns); err != nil {
return nil, err
}
return &types.AttributeValueMemberNS{Value: ns}, nil
case "B":
var b64 string
if err := json.Unmarshal(v, &b64); err != nil {
return nil, err
}
decoded, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return nil, fmt.Errorf("failed to decode base64 for B: %w", err)
}
return &types.AttributeValueMemberB{Value: decoded}, nil
case "BS":
var bs []string
if err := json.Unmarshal(v, &bs); err != nil {
return nil, err
}

decodedBS := make([][]byte, len(bs))
for i, b64 := range bs {
decoded, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return nil, fmt.Errorf("failed to decode base64 for BS: %w", err)
}
decodedBS[i] = decoded
}
return &types.AttributeValueMemberBS{Value: decodedBS}, nil
case "S":
var s string
if err := json.Unmarshal(v, &s); err != nil {
return nil, err
}
return &types.AttributeValueMemberS{Value: s}, nil
case "N":
var n string
if err := json.Unmarshal(v, &n); err != nil {
return nil, err
}
return &types.AttributeValueMemberN{Value: n}, nil
case "BOOL":
var b bool
if err := json.Unmarshal(v, &b); err != nil {
return nil, err
}
return &types.AttributeValueMemberBOOL{Value: b}, nil
case "NULL":
return &types.AttributeValueMemberNULL{Value: true}, nil
case "L":
var list []map[string]json.RawMessage
if err := json.Unmarshal(v, &list); err != nil {
return nil, err
}
var attrList []types.AttributeValue
for _, item := range list {
attr, err := parseAttributeValue(item)
if err != nil {
return nil, err
}
attrList = append(attrList, attr)
}
return &types.AttributeValueMemberL{Value: attrList}, nil
case "M":
var m map[string]map[string]json.RawMessage
if err := json.Unmarshal(v, &m); err != nil {
return nil, err
}
attrMap := make(map[string]types.AttributeValue)
for mk, mv := range m {
attr, err := parseAttributeValue(mv)
if err != nil {
return nil, err
}
attrMap[mk] = attr
}
return &types.AttributeValueMemberM{Value: attrMap}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", k)
}
}
return nil, fmt.Errorf("empty attribute value")
}

func UnmarshalDynamoDBFromJSON(jsonStr []byte) (map[string]types.AttributeValue, error) {
var raw map[string]map[string]json.RawMessage
err := json.Unmarshal(jsonStr, &raw)
if err != nil {
return nil, err
}

result := make(map[string]types.AttributeValue)
for key, value := range raw {
attr, err := parseAttributeValue(value)
if err != nil {
return nil, fmt.Errorf("failed to parse key %s: %w", key, err)
}
result[key] = attr
}

return result, nil
}
95 changes: 95 additions & 0 deletions feature/dynamodb/attributevalue/decode_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package attributevalue

import (
"encoding/base64"
"fmt"
"reflect"
"strconv"
Expand Down Expand Up @@ -1344,3 +1345,97 @@ func TestUnmarshalIndividualSetValues(t *testing.T) {
t.Errorf("expect value match\n%s", diff)
}
}

func TestUnmarshalDynamoDBJSON(t *testing.T) {
msg1 := "Dynamo"
msg2 := "DynamoDB"

bin1 := make([]byte, base64.StdEncoding.EncodedLen(len(msg1)))
bin2 := make([]byte, base64.StdEncoding.EncodedLen(len(msg2)))

base64.StdEncoding.Encode(bin1, []byte(msg1))
base64.StdEncoding.Encode(bin2, []byte(msg2))

actual, err := UnmarshalDynamoDBFromJSON(
[]byte(fmt.Sprintf(`{
"Name": {"S": "Alice"},
"Age": {"N": "30"},
"IsActive": {"BOOL": true},
"Metadata": {"M": {"Key": {"S": "Value"}}},
"Tags": {"L": [{"S": "tag1"}, {"S": "tag2"}]},
"StringSet": {"SS": ["one", "two", "three"]},
"NumberSet": {"NS": ["10", "20", "30"]},
"Binary": {"B": "%s"},
"BinarySet": {"BS": ["%s", "%s"]}
}`, bin1, bin1, bin2),
))
if err != nil {
t.Fatalf("expect no error, got %v", err)
}

if diff := cmpDiff(
&types.AttributeValueMemberS{Value: "Alice"},
actual["Name"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberN{Value: "30"},
actual["Age"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberBOOL{Value: true},
actual["IsActive"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberM{
Value: map[string]types.AttributeValue{
"Key": &types.AttributeValueMemberS{Value: "Value"},
},
},
actual["Metadata"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberL{Value: []types.AttributeValue{
&types.AttributeValueMemberS{Value: "tag1"},
&types.AttributeValueMemberS{Value: "tag2"},
},
},
actual["Tags"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberSS{Value: []string{"one", "two", "three"}},
actual["StringSet"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}
if diff := cmpDiff(
&types.AttributeValueMemberNS{Value: []string{"10", "20", "30"}},
actual["NumberSet"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberB{Value: []byte(msg1)},
actual["Binary"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

if diff := cmpDiff(
&types.AttributeValueMemberBS{Value: [][]byte{
[]byte(msg1),
[]byte(msg2),
},
},
actual["BinarySet"]); diff != "" {
t.Errorf("expect value match\n%s", diff)
}

}

0 comments on commit 0a9b423

Please sign in to comment.