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

DynamoDB AttributeValue's MarshallMap() Does Not Support time.Time Correctly #2383

Open
kmacmcfarlane opened this issue Nov 21, 2023 · 2 comments
Assignees
Labels
feature/dynamodb/attributevalue Pertains to dynamodb attributevalue marshaler HLL (feature/dynamodb/attributevalue). feature-request A feature should be added or improved. p3 This is a minor priority issue queued This issues is on the AWS team's backlog

Comments

@kmacmcfarlane
Copy link

Describe the bug

I am seeing that it's not possible to use omitempty on time.Time struct fields with attributevalue.MarshalMap() from github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue.

This behavior was observed on the latest version: github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.3

Expected Behavior

When MarshalMap() is supplied with a struct containing a time.Time field with the tag omitempty whos value is the zero value, the output map should not contain a key for that struct field.

Current Behavior

Output attribute value map includes a key and value for the field that should be omitted.

Reproduction Steps

repro_test.go

package service

import (
	"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"time"
)

var _ = Describe("Serialize Struct", func() {

	Context("Struct with omitempty on a time.Time field", func() {
		It("Does not serialize the field", func() {

			e := struct {
				Created time.Time `dynamodbav:"created,omitempty"`
			}{
				Created: time.Time{},
			}
			av, err := attributevalue.MarshalMap(e)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(av).ShouldNot(HaveKey("created"))
		})
	})
})

service_test_suite.go

package service_test

import (
	"testing"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

func TestService(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Service Suite")
}

Possible Solution

As far as I've been able to reason by stepping through the repro above in a debugger, there should probably be a check for fieldTag.OmitEmpty in this code block in encode.go (just like the fieldTag.AsUnixTime tag is checked).

if v.Type().ConvertibleTo(timeType) {

Additional Information/Context

No response

AWS Go SDK V2 Module Versions Used

        github.com/aws/aws-lambda-go v1.34.1
        github.com/aws/aws-sdk-go v1.44.128
        github.com/aws/aws-sdk-go-v2 v1.23.1
        github.com/aws/aws-sdk-go-v2/config v1.17.10
        github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.3
        github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.35
        github.com/aws/aws-sdk-go-v2/service/dynamodb v1.25.3
        github.com/aws/aws-sdk-go-v2/service/sfn v1.19.7
        github.com/aws/aws-xray-sdk-go v1.8.0
        github.com/awslabs/aws-lambda-go-api-proxy v0.13.3
        github.com/aws/aws-sdk-go-v2/credentials v1.12.23 // indirect
        github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect
        github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
        github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
        github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
        github.com/aws/aws-sdk-go-v2/service/cloudwatchevents v1.9.2 // indirect
        github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.17.3 // indirect
        github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
        github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.4 // indirect
        github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
        github.com/aws/aws-sdk-go-v2/service/kms v1.18.5 // indirect
        github.com/aws/aws-sdk-go-v2/service/sns v1.20.1 // indirect
        github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
        github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
        github.com/aws/aws-sdk-go-v2/service/sts v1.17.1 // indirect
        github.com/aws/smithy-go v1.17.0 // indirect

Compiler and Version used

go version go1.20.6 linux/amd64

Operating System and version

Fedora Linux 37

@kmacmcfarlane kmacmcfarlane added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Nov 21, 2023
@RanVaknin RanVaknin self-assigned this Nov 21, 2023
@RanVaknin RanVaknin added feature-request A feature should be added or improved. and removed bug This issue is a bug. labels Dec 7, 2023
@RanVaknin
Copy link
Contributor

Hi @kmacmcfarlane ,

The Dynamodb marshallar is built to account for Dynamodb native types (BOOL,SS,N,S,L, etc..) time.Time is not one of them. Thats why there is no out of the box functionality to support this. I have converted this to a feature request and added it to our backlog until it gets prioritized.

In the meantime, as a workaround, you can handle your time.Time data by converting it into a string or an epoch number, both of which are supported by the encoder. For example, you can format the time.Time as an ISO 8601 string, or you can convert it to a UNIX timestamp (epoch number). Something like

epochNumber := yourTime.Unix()

Thanks,
Ran~

@RanVaknin RanVaknin added p3 This is a minor priority issue queued This issues is on the AWS team's backlog and removed needs-triage This issue or PR still needs to be triaged. labels Dec 7, 2023
@kmacmcfarlane
Copy link
Author

@RanVaknin This is surprising, since the godocs indicate time.RFC3339Nano is the default encoding for time.Time in Marshal() and the EncoderOptions docs indicate if you don't supply a EncodeTime func it will use time.RFC3339Nano as well. If I call MarshalMapWithOptions() with default options, I would expect it to encode time.Time fields as time.RFC3339Nano strings.

Further the docs state

MarshalMapWithOptions is an alias for MarshalWithOptions func which marshals Go value type to a map of AttributeValues. If the in parameter does not serialize to a map, an empty AttributeValue map will be returned.

It feels like it shouldn't behave any different than a regular Marshal() which is why I think it's a bug.

@lucix-aws lucix-aws added the feature/dynamodb/attributevalue Pertains to dynamodb attributevalue marshaler HLL (feature/dynamodb/attributevalue). label Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/dynamodb/attributevalue Pertains to dynamodb attributevalue marshaler HLL (feature/dynamodb/attributevalue). feature-request A feature should be added or improved. p3 This is a minor priority issue queued This issues is on the AWS team's backlog
Projects
None yet
Development

No branches or pull requests

3 participants