如何从 DynamoDB 的 Golang SDK 序列化 `LastEvaluatedKey`?

Dan*_*Dan 6 go amazon-dynamodb

在 Golang 中使用 DynamoDB 时,如果对 的调用query有更多结果,它将LastEvaluatedKey在 上设置QueryOutput,然后您可以将其传递到下一次调用 asqueryExclusiveStartKey从上次中断的位置继续。

当值保留在 Golang 中时,这非常有效。但是,我正在编写一个分页 API 端点,因此我想序列化此密钥,以便我可以将其作为分页令牌返回给客户端。像这样的东西,something做我想要的事情的神奇包在哪里:

type GetDomainObjectsResponse struct {
  Items     []MyDomainObject `json:"items"`
  NextToken string           `json:"next_token"`
}
  
func GetDomainObjects(w http.ResponseWriter, req *http.Request) {
  // ... parse query params, set up dynamoIn ...

  dynamoIn.ExclusiveStartKey = something.Decode(params.NextToken)

  dynamoOut, _ := db.Query(dynamoIn)

  response := GetDomainObjectsResponse{}
  dynamodbattribute.UnmarshalListOfMaps(dynamoOut.Items, &response.Items)

  response.NextToken := something.Encode(dynamoOut.LastEvaluatedKey)
  
  // ... marshal and write the response ...
}
Run Code Online (Sandbox Code Playgroud)

(请原谅上面的任何拼写错误,这是我为了隔离问题而快速编写的代码的玩具版本)

因为我需要支持具有不同搜索模式的多个端点,所以我希望有一种生成不依赖于特定搜索键的分页标记的方法。

问题是,我还没有找到一种干净且通用的方法来序列化LastEvaluatedKey. 您可以将其直接编组为 JSON(然后例如对其进行 base64 编码以获取令牌),但这样做是不可逆的。LastEvaluatedKey是一个map[string]types.AttributeValue, 并且types.AttributeValue是一个接口,因此虽然 json 编码器可以读取它,但无法写入它。

例如,以下代码会因panic: json: cannot unmarshal object into Go value of type types.AttributeValue.

lastEvaluatedKey := map[string]types.AttributeValue{
    "year":  &types.AttributeValueMemberN{Value: "1993"},
    "title": &types.AttributeValueMemberS{Value: "Benny & Joon"},
}

bytes, err := json.Marshal(lastEvaluatedKey)
if err != nil {
  panic(err)
}

decoded := map[string]types.AttributeValue{}
err = json.Unmarshal(bytes, &decoded)
if err != nil {
  panic(err)
}
Run Code Online (Sandbox Code Playgroud)

我想要的是一种直接使用 DynamoDB 风格的 JSON 的方法,就像在 CLI 上运行时得到的那样aws dynamodb query。不幸的是golang SDK不支持这个

我想我可以为 AttributeValue 类型编写自己的序列化器/反序列化器,但这比这个项目应有的努力要多。

有没有人找到一个通用的方法来做到这一点?

Dan*_*Dan 6

好吧,我想出了一些办法。

type GetDomainObjectsResponse struct {
  Items     []MyDomainObject `json:"items"`
  NextToken string           `json:"next_token"`
}
  
func GetDomainObjects(w http.ResponseWriter, req *http.Request) {
  // ... parse query params, set up dynamoIn ...

  eskMap := map[string]string{}
  json.Unmarshal(params.NextToken, &eskMap)
  esk, _ = dynamodbattribute.MarshalMap(eskMap)
  dynamoIn.ExclusiveStartKey = esk

  dynamoOut, _ := db.Query(dynamoIn)

  response := GetDomainObjectsResponse{}
  dynamodbattribute.UnmarshalListOfMaps(dynamoOut.Items, &response.Items)

  lek := map[string]string{}
  dynamodbattribute.UnmarshalMap(dynamoOut.LastEvaluatedKey, &lek)
  response.NextToken := json.Marshal(lek)
  
  // ... marshal and write the response ...
}

Run Code Online (Sandbox Code Playgroud)

(这又是我匆忙转回玩具问题的真正解决方案,所以请原谅任何拼写错误)

正如@buraksurdar 指出的,attributevalue.Unmarshal需要一个inteface{}. 事实证明,除了具体类型之外,您还可以传入map[string]string,并且它就可以工作。

我相信如果不平坦,这将不起作用AttributeValue,所以这不是一个通用的解决方案[需要引用]。但我的理解是,LastEvaluatedKey调用返回的值Query始终是平坦的,因此它适用于这个用例。