Aws-sdk-net: DynamoDB conversion from document to dictionary

Created on 12 May 2017  路  7Comments  路  Source: aws/aws-sdk-net

Using a Dictionary<string, object> in a class that is to be stored in DynamoDB requires a custom property converter that is not very elegant

Current Behavior

I have a very simple class in a shared library with looks roughly like this

public class ServiceInfo
{
   public string Region { get; set; }
   public string Name { get; set; }
   public Dictionary<string, object> AdditionalProperties { get; set; }
}

The only way I can find of being able to use this object is by specifying a custom converter for the property. My issue with this is that my shared library now requires a dependency on AWS SDK which is not ideal and I have to use refection to find the existing converter for a type as there is no public DynamoDBEntryConversion.ConvertToEntry which takes the type as an argument

public class ServiceInfo
{
   public string Region { get; set; }
   public string Name { get; set; }
   [DynamoDBProperty(typeof(DictionaryTypeConverter))]
   public Dictionary<string, object> AdditionalProperties { get; set; }
}

class DictionaryTypeConverter : IPropertyConverter
{
    private static readonly Func<object, DynamoDBEntry> Convert;

    static DictionaryTypeConverter()
    {
        var method = typeof(DynamoDBEntryConversion).GetMethod(nameof(DynamoDBEntryConversion.ConvertToEntry));

        Convert = o =>
        {
            var generic = method.MakeGenericMethod(o.GetType());
            return (DynamoDBEntry)generic.Invoke(DynamoDBEntryConversion.V2, new[] { o });
        };
    }

    public DynamoDBEntry ToEntry(object value)
    {
        var dictionary = value as IDictionary<string, object>;
        if (dictionary == null)
            throw new InvalidOperationException();

        var d = new Document();
        foreach (var keyValuePair in dictionary)
        {
            var key = keyValuePair.Key;
            var obj = keyValuePair.Value;
            var dynamoDbEntry = (DynamoDBEntry)null;
            if (obj != null)
                dynamoDbEntry = Convert(obj);
            d[key] = dynamoDbEntry;
         }
         return d;
    }

    public object FromEntry(DynamoDBEntry entry)
    {
        var d = entry as Document;
        if (d == null)
            throw new InvalidOperationException();

        var result = new Dictionary<string, object>();
        foreach (var keyValuePair in d)
        {
            result.Add(keyValuePair.Key, ConvertValue(keyValuePair.Value));
        }
        return result;
    }

    private object ConvertValue(DynamoDBEntry value)
    {
        var primitive = value as Primitive;
        if (primitive != null)
        {
            switch (primitive.Type)
            {
                case DynamoDBEntryType.String:
                    return value.AsString();
                case DynamoDBEntryType.Numeric:
                {
                    int integer;
                    return DynamoDBEntryConversion.V1.TryConvertFromEntry(value, out integer) ? integer : value.AsDouble();
                }
                case DynamoDBEntryType.Binary:
                    return value.AsByteArray();
            }
        }
        else if (value is DynamoDBList)
        {
            var result = new List<object>();
            var list = (DynamoDBList)value;
            foreach (var item in list.Entries)
            {
                result.Add(ConvertValue(item));
            }
            return result;
        }
        else if (value is DynamoDBBool)
        {
            return value.AsBoolean();
        }
        else if (value is DynamoDBNull)
        {
            return null;
        }
        else if (value is Document)
        {
            return FromEntry(value);
        }
        throw new InvalidOperationException();
    }
}

Possible Solutions

  • Change DictionaryConverterV1 to support TryFrom
  • Allow a custom converter to be registered in DynamoDBEntryConversion (This is currently all internal)
guidance

Most helpful comment

FWIW, I ran into this issue myself recently and it's quite the surprise. I would expect, of all things, Dictionary to be supported as a M (map) property easily, and Dictionary seems just as straight forward. Kind of weak-sauce IMHO (i.e. arbitrary) to work with List<>, etc. and not dictionaries.

All 7 comments

You can use the ServiceInfo object without annotating it with DynamoDBProperty or including a reference to the SDK in the project that defines ServiceInfo. See the below code sample showing how an un-annotated class can use a customer converter.

// Define converter for a property
var dataPropertyConfig = new Amazon.Util.PropertyConfig("AdditionalProperties ");
dataPropertyConfig.Converter = typeof(DictionaryTypeConverter);

// Define type-to-table mapping
var typeMapping = new Amazon.Util.TypeMapping(typeof(ServiceInfo), "MyAmazingTable");
// Add property that needs to be customized
typeMapping.AddProperty(dataPropertyConfig);

// Add global mapping
AWSConfigsDynamoDB.Context.AddMapping(typeMapping);

This information can also be specified inside an app.config/web.config file. You can see an example of that here.

Will that approach work for you?

That is a much nicer way of registering the converter I will give it a try, however this approach still won't fix the need for using reflection in the property converter

True.

Is there a reason to use the Object Persistence Model (DynamoDBContext) for the data in question? OPM is good for cases where all of the data is modeled, but AdditionalProperties clearly isn't.
A more appropriate DynamoDB API would be the Document Model. Since you are already dealing with DynamoDBEntry objects and already need to cast the data coming out of AdditionalProperties, loading a Document object instead of ServiceInfo may save you a lot of hassle.

I may use the Document object but I would still need to covert to a ServiceInfo instance as that's what my API requires

Closing for lack of activity

FWIW, I ran into this issue myself recently and it's quite the surprise. I would expect, of all things, Dictionary to be supported as a M (map) property easily, and Dictionary seems just as straight forward. Kind of weak-sauce IMHO (i.e. arbitrary) to work with List<>, etc. and not dictionaries.

Any pointers on how to convert Document to List<Dictionary<string, object>>?
SDK was able to convert List> to Document and save it in Dynamodb but it fails when I'm trying to get data from Dynamodb.

Code that I'm working for reference looks something like this:

using (DynamoDBContext dynamoDBContext = new DynamoDBContext(_dynamoDBClient))
{
 return await dynamoDBContext.QueryAsync<Myclass>(Parameter).GetRemainingAsync().ConfigureAwait(false);
}
Was this page helpful?
0 / 5 - 0 ratings