Convert Dictionary<string,object> to Amazon.AttributeValue

So I’m trying to serialize/deserialize from DynamoDb Table Property (Dictionary< string,object> Payload {get;set;}) with the idea that the Key would be the Type, and the Value would be the object to serialize.

Example:

I can serialize into DynamoDb no problem, and looking at the above example, it looks fine. The Payload has one type (JobRequestor) and the properties of JobRequestor are there.

I’m having problems deserializing back out because the data comes back encoded as Amazon AttributeValues which throw an error trying to use .FromObjectDictionary< AttributeValuie>()

{
  "Enumis.Api.Jobs.ServiceModel.Types.JobRequestor": {
    "ApprovalState": {
      "__type": "Amazon.DynamoDBv2.Model.AttributeValue, AWSSDK.DynamoDBv2",
      "bool": false,
      "isBOOLSet": false,
      "bs": [],
      "l": [],
      "isLSet": false,
      "m": {},
      "isMSet": false,
      "ns": [],
      "null": false,
      "s": "Pending",
      "ss": []
    },
    "RequestedCognitoEmail": {
      "bool": false,
      "isBOOLSet": false,
      "bs": [],
      "l": [],
      "isLSet": false,
      "m": {},
      "isMSet": false,
      "ns": [],
      "null": false,
      "s": "you@wish.com",
      "ss": []
    }
  }
}

I am trying to create a generic PayloadDeserializer that looks at the root object containing the Payload dictionary, and if any properties on this root object match the type in the Payload dictionary keys, deserialize the object back into the property on the root object.

My current attempt looks something like this (keep in mind I’m just trying to work the process out here):

    var genericJob = ServiceClient.Get(new JobRequest {ERN = "ern::Enumis::GpsCreateCardsJob::8a24cfc0-d326-4de7-8534-812b92f89d8b"});

    var _job = genericJob.ConvertTo<ApprovalJob>();
    var requestor = _job.Payload[typeof(JobRequestor).FullName] as Dictionary<string,object>;
    var state = requestor["ApprovalState"] as Dictionary<string, object>;
    var attr = state.FromObjectDictionary<AttributeValue>();

Which when I run I get the following error:

System.IndexOutOfRangeException : Index was outside the bounds of the array.
   at System.String.get_Chars(Int32 index)
   at ServiceStack.StringExtensions.ToPascalCase(String value)
   at ServiceStack.PlatformExtensions.FromObjectDictionary(IReadOnlyDictionary`2 values, Type type)
   at ServiceStack.PlatformExtensions.FromObjectDictionary[T](IReadOnlyDictionary`2 values)
   at Enumis.Api.Jobs.Tests.IntegrationTest.Can_Serialize_Deserialize_Payload() in D:\Dev\enumis.api.jobs\src\Enumis.Api.Jobs.Tests\IntegrationTest.cs:line 70

This seems to be caused by the property

  "__type": "Amazon.DynamoDBv2.Model.AttributeValue, AWSSDK.DynamoDBv2",

When I remove the __type from the dictionary, I can get a little farther, but it still blows up on the property:

  "m": {},

With the following error:

System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.Dictionary`2[System.String,Amazon.DynamoDBv2.Model.AttributeValue]' to type 'System.Collections.Generic.ICollection`1[System.String]'.
   at ServiceStack.Text.TranslateListWithConvertibleElements`2.TranslateToGenericICollection(ICollection`1 fromList, Type toInstanceOfType)
   at ServiceStack.Text.TranslateListWithConvertibleElements`2.LateBoundTranslateToGenericICollection(Object fromList, Type toInstanceOfType)
   at ServiceStack.Text.TranslateListWithElements.TranslateToConvertibleGenericICollectionCache(Object from, Type toInstanceOfType, Type fromElementType)
   at ServiceStack.Text.TranslateListWithElements.TryTranslateCollections(Type fromPropertyType, Type toPropertyType, Object fromValue)
   at ServiceStack.TypeConverter.<>c__DisplayClass0_0.<CreateTypeConverter>b__4(Object fromValue)
   at ServiceStack.PlatformExtensions.ObjectDictionaryFieldDefinition.SetValue(Object instance, Object value)
   at ServiceStack.PlatformExtensions.FromObjectDictionary(IReadOnlyDictionary`2 values, Type type)
   at ServiceStack.PlatformExtensions.FromObjectDictionary[T](IReadOnlyDictionary`2 values)
   at Enumis.Api.Jobs.Tests.IntegrationTest.Can_Serialize_Deserialize_Payload() in D:\Dev\enumis.api.jobs\src\Enumis.Api.Jobs.Tests\IntegrationTest.cs:line 71

Question: What is the best way to achieve this, and how can I deserialize this data back into the concrete property type it should be?

Update 1:

So I’m looking at the differences between the AttributeValue, and what comes back from the DynamoDb Get.

When I run this code:

    var att1 = new AttributeValue {S = "Pending"};
    var dic1 = att1.ToObjectDictionary();
    var att2 = dic1.FromObjectDictionary<AttributeValue>();

I can deserialize no problem, and the Dictionary looks like:

But the dictionary coming back from DynamoDb looks like:

Which throws the error trying to execute:

var attr = state.FromObjectDictionary<AttributeValue>();

Any ideas?

You can’t use ToObjectDictionary/FromObjectDictionary on DynamoDB’s AttributeValue, it needs a number of Attribute Value converters and the Metadata Type of the table to be able to convert it and deserialize it into different type. ServiceStack.Aws handles most of this conversion in the DynamoConverters.cs class.

You can access the Converters from a PocoDynamo client with db.Converters. Have a look at how PocoDynamo uses Converters to serialize and deserialize into POCOs, e.g. with its PutItem() method:

public T PutItem<T>(T value, bool returnOld = false)
{
    var table = DynamoMetadata.GetTable<T>();
    var request = new PutItemRequest
    {
        TableName = table.Name,
        Item = Converters.ToAttributeValues(this, value, table),
        ReturnValues = returnOld ? ReturnValue.ALL_OLD : ReturnValue.NONE,
    };

    var response = Exec(() => DynamoDb.PutItem(request));

    if (response.Attributes.IsEmpty())
        return default(T);

    return Converters.FromAttributeValues<T>(table, response.Attributes);
}

Where it uses Converters.ToAttributeValues() to convert into Attribute Values and Converters.FromAttributeValues<T>() to convert back from values into a C# POCO.