Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnmarshalTypeError: cannot unmarshal map string key into Go value of type xx #3394

Closed
3 tasks done
scorsi opened this issue Jun 26, 2020 · 7 comments
Closed
3 tasks done
Labels
feature-request A feature should be added or improved. needs-contributors

Comments

@scorsi
Copy link

scorsi commented Jun 26, 2020

Describe the bug
Looks like #1917 but everything I made is correct. Marshaling/unmarshalling when my enums are not in map string key with the same process are fine.

See the following example for a concret example. It looks like when marshal/unmarshall map, the string key aren't marshaled/unmarshaled and it expects to be a string.

Version of AWS SDK for Go?
v1.32.10

Version of Go (go version)?
go1.14.4 darwin/amd64

To Reproduce (observed behavior)

// not working code, looks bellow in the next @scorsi comment

Expected behavior
I can marshal my Order but when I look into DynamoDB the key aren't marshalled, it's why it failed during unmarshaling.

@scorsi scorsi added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Jun 26, 2020
@diehlaws diehlaws self-assigned this Jun 30, 2020
@diehlaws diehlaws removed the needs-triage This issue or PR still needs to be triaged. label Jun 30, 2020
@diehlaws
Copy link
Contributor

Thanks for reaching out to us about this @scorsi. Unfortunately I'm not able to reproduce this with the provided code since your order and FilterType types are not being defined. Based on the contents of the code sample I'm guessing the order type should contain the Order struct defined in your snippet along with FilterType, however in the interest of avoiding unpredictable results I'd prefer to be sure things are being defined correctly when attempting to reproduce this instead of guessing based on context.

$ go run main.go 
# command-line-arguments
./main.go:14:21: undefined: FilterType
./main.go:18:30: undefined: FilterType
./main.go:21:37: undefined: FilterType
./main.go:26:9: undefined: FilterType
./main.go:36:10: undefined: FilterType
./main.go:52:14: undefined: FilterType
./main.go:74:16: undefined: order
./main.go:81:17: undefined: order
./main.go:84:42: undefined: order
./main.go:112:3: too many arguments to return
        have (nil)
        want ()
./main.go:112:3: too many errors

Can you please update the code sample to define these types so we can better troubleshoot the described behavior?

@diehlaws diehlaws added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jun 30, 2020
@scorsi
Copy link
Author

scorsi commented Jul 1, 2020

Hello, thanks for your reply ! Sorry I didn't check if the sample works or not..

Here's a working sample:

package main

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

const tableName = "MyTable"

var (
	svc *dynamodb.DynamoDB = nil
)

func init() {
	sess := session.Must(session.NewSessionWithOptions(session.Options{
		SharedConfigState: session.SharedConfigEnable,
	}))
	svc = dynamodb.New(sess)
}

type MyEnum int

const (
	MyEnumA MyEnum = iota
	MyEnumB
)

var (
	_myEnumValueToName = map[MyEnum]string{
		MyEnumA: "A",
		MyEnumB: "B",
	}
	_myEnumNameToValue = map[string]MyEnum{
		"A": MyEnumA,
		"B": MyEnumB,
	}
)

func (t MyEnum) MarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error {
	if s, ok := _myEnumValueToName[t]; !ok {
		return fmt.Errorf("invalid MyEnum: %d", t)
	} else {
		av.S = aws.String(s)
		return nil
	}
}

func (t *MyEnum) UnmarshalDynamoDBAttributeValue(av *dynamodb.AttributeValue) error {
	s := aws.StringValue(av.S)
	if v, ok := _myEnumNameToValue[s]; !ok {
		return fmt.Errorf("invalid MyEnum %q", s)
	} else {
		*t = v
		return nil
	}
}

type MyStruct struct {
	Id     string
	MyEnum MyEnum
	MyMap  map[MyEnum]bool
}

func setItem(id string) {
	s := MyStruct{
		Id:     id,
		MyEnum: MyEnumA,
		MyMap: map[MyEnum]bool{
			MyEnumA: true,
			MyEnumB: false,
		},
	}

	fmt.Println(s)

	av, err := dynamodbattribute.MarshalMap(s)
	if err != nil {
		panic(err)
	}

	input := &dynamodb.PutItemInput{
		Item:      av,
		TableName: aws.String(tableName),
	}

	_, err = svc.PutItem(input)
	if err != nil {
		panic(err)
	}
}

func getItem(id string) {
	result, err := svc.GetItem(&dynamodb.GetItemInput{
		TableName: aws.String(tableName),
		Key: map[string]*dynamodb.AttributeValue{
			"Id": {
				S: aws.String(id),
			},
		},
	})
	if err != nil {
		panic(err)
	}

	s := MyStruct{}

	err = dynamodbattribute.UnmarshalMap(result.Item, &s)
	if err != nil {
		panic(fmt.Sprintf("Failed to unmarshal Record, %v", err))
	}

	fmt.Println(s)
}

func main() {
	id := "0"
	setItem(id)
	getItem(id)
}

You just need to change tableName (just after the import) to a freshly created DynamoDB Table with "Id" as primary partition key.
You can also change the id var at the top of the main function if you wish.

You will got the following output:

{0 0 map[0:true 1:false]}
panic: Failed to unmarshal Record, UnmarshalTypeError: cannot unmarshal map string key into Go value of type main.MyEnum

goroutine 1 [running]:
main.getItem(0x14948c5, 0x1)
	prometer-enrichment/testLambda/main.go:112 +0x389
main.main()
	prometer-enrichment/testLambda/main.go:121 +0x4f

If you look into your DynamoDB Table you will see the following entry:

{
  "Id": {
    "S": "0"
  },
  "MyEnum": {
    "S": "A"
  },
  "MyMap": {
    "M": {
      "0": {
        "BOOL": true
      },
      "1": {
        "BOOL": false
      }
    }
  }
}

As you can see, it's "0" and "1" instead of "A" and "B", since my Marshalling will translate MyEnumA = 0 to "A" and vice-versa.
I added MyEnum to show you that it works when outside a key map.
Even if you manually fix the key to "A" and "B" and tried to only getItem(id) you will have the same error (don't forget to comment the setItem(id) in the main before).

@scorsi
Copy link
Author

scorsi commented Jul 1, 2020

My actual work-around is to do as follow:

type MyEnum string
const (
	MyEnumA MyEnum = "A"
	MyEnumB MyEnum = "B"
)

And removing both *MarshalDynamoDBAttributeValue methods and both _myEnum* variables.

But I would like to have ints in my Go code since it's lightweight.
And I need to have strings in my table since another Lambdas in different languages are writing into it.

And using strings instead of int enums don't checks out-of-bound value (example: if I set "C", an invalid enum value, in the table).

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jul 2, 2020
@diehlaws diehlaws removed their assignment Aug 26, 2020
@botchris
Copy link

botchris commented Sep 3, 2020

I'm having this same issue.

Map keys are always converted to string when encoding. And Map decoder logic seems to raise an error if destination map's key is not a string. So basically there is no way to encode/decode maps indexed by a non-string type (e.g. map[int]int).

https://github.com/aws/aws-sdk-go/blob/master/service/dynamodb/dynamodbattribute/encode.go#L336
https://github.com/aws/aws-sdk-go/blob/master/service/dynamodb/dynamodbattribute/decode.go#L476

@alexrudd
Copy link

This is something that the standard library supports in its json marshaler, so should be possible: https://play.golang.org/p/mUDdAiYZAnz

Looks like this is the code for converting the key string to the correct type: https://golang.org/src/encoding/json/decode.go#L786

@skmcgrail skmcgrail added feature-request A feature should be added or improved. needs-contributors and removed bug This issue is a bug. labels Mar 26, 2021
@skotambkar
Copy link
Contributor

We do not have plans to add support in V1 SDK. Feel free to open a PR in the aws/aws-sdk-go-v2 SDK.

This feature request is related to aws/aws-sdk-go-v2#645 and aws/aws-sdk-go-v2#411

@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved. needs-contributors
Projects
None yet
Development

No branches or pull requests

6 participants