hope someone can shed some light on this issue I've been trying to wrap my head around.
I've got this table in Dynamo, let's call it people
and in this table, I've got the attributes of id
as our partition key, name
, lastName
, and status
.
I'd like to be able to just update either a single attribute or all of them save for the ID.
Now, this is how I've gone about it. I've created the following struct:
type PersonUpdate struct {
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
Status string `json:"status,omitempty"`
}
And the request coming from the server would be to just update the person's last name, so our request body would look as follows:
{
"lastName": "bob"
}
After we bind our request to our struct the object that would be sent to dynamo looks as as such:
{
"firstName": "",
"lastName": "bob",
"status": "",
}
Now when it's time to write to dynamo as you can see, only one attribute should be updated while the rest which are empty/null should be ignored.
The code written to perform this action can be condensed to the following actions:
// Marshal our object
_, err := dynamodbattribute.MarshalMap(person)
if err != nil{
fmt.Println("some error marshaling")
}
// Create our update input
input := &dynamodb.UpdateItemInput{
key: map[string]*dynamodb.AttributeValue{
"id":{
S: aws.String("1234"),
},
},
TableName: aws.String("people"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":fn": {
S: aws.String(person.FirstName),
},
":ln":{
S: aws.String(person.LastName),
},
":st":{
S: aws.String(person.Status),
},
},
UpdateExpression: aws.String("set firstName = :fn, lastName = :ln, status = :st"),
ReturnValues: aws.String("UPDATED_NEW"),
}
// Send update request to Dynamo
_, err := service.UpdateItem(input)
Now, the update goes through with no issues, the problem is that the values for firstName
and status
which are null are getting passed as well. I've tried to go through their documentation but left a bit more confused. I know for a fact the Java SDK allows you to pass a flag called UPDATE_SKIP_NULL_ATTRIBUTES
which allows you to skip those empty values and only update those which have data. I don't know what would be the equivalent in Go, any assistance/guidance would be great.
Update:
Tried to use the following model:
type UserModel struct {
FirstName string `json:"firstName,omitempty" dynamodbav:"firstName,omitempty"`
LastName string `json:"lastName,omitempty" dynamodbav:"lastName,omitempty"`
}
following the suggestion given by @jarmod and @fedonev. Logically and using the docs it makes sense why this should work, unfortunately, it didn't
Decided to switch the SDK from V1 to V2 and see if maybe updating it would help, again though I'm in the same hole. This is what my update function looks like.
update :=expression.Set(expression.Name("firstName"),expression.Value(user.FirstName))
update.Set(expression.Name("lastName"), expression.Value(user.LastName))
expr, err := expression.NewBuilder().WithUpdate(update).Build()
if err != nil {
"log error here..."
}
_, err = svc.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("people"),
Key: map[string]types.AttributeValue{"id": id},
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
UpdateExpression: expr.Update(),
ReturnValues: types.ReturnValueUpdatedNew,
})
if err != nil {
"log error here..."
}
I ended up solving my problem by writing a query-and-create function, which pretty much queries by the ID that we'd like to update takes in the JSON payload, and make a diff of the query against what we need to update and any empty fields get replaced by the query results. So far it solves my problem but I still would like to know how to go about doing an update using its intended function.
As @jarmod's comment says, apply the dynamodbav:",omitempty"
tag to skip zero-value fields when marshaling the struct:
type PersonUpdate struct {
FirstName string `json:"firstName,omitempty" dynamodbav:",omitempty"`
LastName string `json:"lastName,omitempty" dynamodbav:",omitempty"`
Status string `json:"status,omitempty" dynamodbav:",omitempty"`
}
[Edit: add usage] MarshalMap
will now ignore zero-value fields, respecting the tags. Iterate over the map to construct the update expression:
av, _ := attributevalue.MarshalMap(person)
update := expression.UpdateBuilder{}
for k, v := range av {
update = update.Set(expression.Name(k), expression.Value(v))
}
Build the expression and pass its outputs to UpdateItem
, as in the OP.