Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize an object of type T from Dictionary<string,object>

I'm looking for more generic/"standard" way to instantiate object of some type T from set of pairs string,object. For me it looks like there should be some well known way to do it but I cannot find it, so I come up with this piece of code. Does anybody know something better?

// usage
public class test
    {
    public int field1;
    public string field2;
    public bool field3;
    public string[] field4;
    public IDictionary<string,object> field5 { get; set; }

    public static IDictionary<string,object> dynamic()
        {
        return new Dictionary<string,object>{
            { "field1", 2 },
            { "field2", "string" },
            { "field3", true },
            { "field4", new[] { "id3", "id4", "id5" } },
            { "field5", new Dictionary<string,object>{ { "id1", "" } } }
            };
        }
    }

...
var r = new dynamic_data_serializer<test>().create( test.dynamic() );
...

// 
public class dynamic_data_serializer< T >
    {
    public T create( object obj )
        {
        var result = default(T);
        if ( obj == null )
            return result;

        var ttype = typeof(T);
        var objtype = obj.GetType();
        if ( ttype.IsAssignableFrom( objtype ) ) {
            result = (T)obj;
            return result;
            }

        if ( ttype.IsClass ) { // custom classes, array, dictionary, etc.
            result = Activator.CreateInstance<T>();

            if ( objtype == typeof(IDictionary<string,object>) || 
                    objtype == typeof(Dictionary<string,object>) ) {
                var obj_as_dict = obj as IDictionary<string,object>; 
                var fields = ttype.GetFields();
                if ( fields.Length > 0 )
                    set_fields_from( result, fields, obj_as_dict );

                var properties = ttype.GetProperties();
                if ( properties.Length > 0 )
                    set_properties_from( result, properties, obj_as_dict );
                }
            }    
        return result;
        }

    private void set_fields_from( T _this_, FieldInfo[] fields, IDictionary<string,object> obj ) {
        foreach ( var fld in fields ) {
            var v = find( obj, fld.Name );
            if ( v != null ) {
                var mobj = call_deserialize( fld.FieldType, v );
                fld.SetValue( _this_, mobj );
                }
            }
        }

    private void set_properties_from( T _this_, PropertyInfo[] properties, IDictionary<string,object> obj ) {
        foreach ( var prop in properties ) {
            var v = find( obj, prop.Name );
            if ( v != null ) {
                var mobj = call_deserialize( prop.PropertyType, v );
                prop.SetValue( _this_, mobj, null );
                }
            }
        }

    private object find( IDictionary<string,object> obj, string name ) {
        foreach ( var kv in obj )
            if ( string.Compare( kv.Key, name, true ) == 0 )
                return kv.Value;
        return null;
        }

    private object call_deserialize( Type des_type, object value ) {
        var gtype = typeof(dynamic_data_serializer<>);
        Type desz_type = gtype.MakeGenericType( new[]{ des_type } );
        object desz = Activator.CreateInstance( desz_type );
        var method_type = desz_type.GetMethod( "create" ); 
        return method_type.Invoke( desz, new[]{ value } );
        }
    }
}

like image 912
AC. Avatar asked Mar 01 '10 02:03

AC.


2 Answers

DataContractJsonSerializer is too slow, but you're using reflection? If you have to deserialize lots of objects, I would recommend using compiled lambdas instead of reflection. A lambda can only set properties, not fields (at least in .Net 3.5), so you may have to adjust the classes you use it on, but it's worth it because it's like 1000 times faster.

Here's a function that creates a property setter given a type and a PropertyInfo for the property to set:

    static Action<object, TValue> MakeSetter<TValue>(Type tclass, PropertyInfo propInfo)
    {
        var t = lambda.Expression.Parameter(typeof(object), "t");
        var v = lambda.Expression.Parameter(typeof(TValue), "v");
        // return (t, v) => ((tclass)t).prop = (tproperty)v
        return (Action<object, TValue>)
            lambda.Expression.Lambda(
                lambda.Expression.Call(
                    lambda.Expression.Convert(t, tclass),
                    propInfo.GetSetMethod(),
                    lambda.Expression.Convert(v, propInfo.PropertyType)),
                t,
                v)
            .Compile();
    }

You would have a dictionary of setters for each class, and whenever you have to set a property of a class, you would look up the setter for that property in the dictionary and call it with the value to assign, like this: setters[propName](_this_, value);

like image 82
Gabe Avatar answered Sep 22 '22 10:09

Gabe


I might suggest FormatterServices.PopulateObjectMembers, except a: this is still slow AFAIK, and b: I tried it (below) and it seems to want to throw an exception on the property (don't know why; didn't look too deep). Another option may be Expression, but you don't really want to do the Compile each time (better to do it once only and cache it, but that demands a known format).

public T create(object obj)
{  // simplified for illustration
    var bindings = obj as IDictionary<string, object>;
    Type type = typeof(T);
    var func = Expression.Lambda<Func<T>>(Expression.MemberInit(
        Expression.New(type),
        from pair in bindings
        let member = type.GetMember(pair.Key).Single()
        select (MemberBinding)Expression.Bind(member, Expression.Constant(pair.Value))));
    return func.Compile().Invoke();
}

Finally, you might cache a set of pre-compiled Action<object> setters (keyed against the member name). In reality this is probably your best bet. Properties are easy (you use Delegate.CreateDelegate) - fields might need DynamicMethod - but if you can't predict the layout in advance it'll have the least overhead.

For the keyed / IL approach (you won't get faster):

public class dynamic_data_serializer<T>
{
    public T create(object obj)
    {
        T inst = Activator.CreateInstance<T>();
        var bindings = obj as IDictionary<string, object>;
        foreach (var pair in bindings)
        {
            setters[pair.Key](inst, pair.Value);
        }
        return inst;
    }
    private static readonly Dictionary<string, Action<T, object>> setters;
    static dynamic_data_serializer()
    {
        setters = new Dictionary<string, Action<T, object>>(StringComparer.Ordinal);
        foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
            setters.Add(prop.Name, CreateForMember(prop));
        }
        foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)) {
            setters.Add(field.Name, CreateForMember(field));
        }
    }
    static Action<T, object> CreateForMember(MemberInfo member)
    {
        bool isField;
        Type type;
        switch (member.MemberType) {
            case MemberTypes.Property:
                isField = false;
                type = ((PropertyInfo)member).PropertyType;
                break;
            case MemberTypes.Field:
                isField = true;
                type = ((FieldInfo)member).FieldType;
                break;
            default:
                throw new NotSupportedException();
        }
        DynamicMethod method = new DynamicMethod("__set_" + member.Name, null, new Type[] { typeof(T), typeof(object) });
        ILGenerator il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        if(type != typeof(object)) {
            il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
        }
        if (isField) {il.Emit(OpCodes.Stfld, (FieldInfo)member);}
        else { il.EmitCall(OpCodes.Callvirt, ((PropertyInfo)member).GetSetMethod(), null);  }

        il.Emit(OpCodes.Ret);
        return (Action<T, object>)method.CreateDelegate(typeof(Action<T, object>));
    }
}
like image 31
Marc Gravell Avatar answered Sep 23 '22 10:09

Marc Gravell