Expression.Convert: Object of type 'System.Int64' cannot be converted to type 'System.Int32'

15,861

Solution 1

The expression is correct. The problem is Json.NET. It converts all numeric values (in anonymous conversions) to Int64. So, I just need a custom convertor:

public class JsonIntegerConverter : JsonConverter {

    public override bool CanConvert(Type objectType) {
        return objectType == typeof(IDictionary<string, object>);
    }

    public override bool CanWrite {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        var result = new Dictionary<string, object>();
        reader.Read();

        while (reader.TokenType == JsonToken.PropertyName) {
            var propertyName = (string)reader.Value;
            reader.Read();
            object value;
            if (reader.TokenType == JsonToken.Integer) {
                var temp = Convert.ToInt64(reader.Value);
                if (temp <= Byte.MaxValue && temp >= Byte.MinValue)
                    value = Convert.ToByte(reader.Value);
                else if (temp >= Int16.MinValue && temp <= Int16.MaxValue)
                    value = Convert.ToInt16(reader.Value);
                else if (temp >= Int32.MinValue && temp <= Int32.MaxValue)
                    value = Convert.ToInt32(reader.Value);
                else
                    value = temp;
            } else
                value = serializer.Deserialize(reader);
            result.Add(propertyName, value);
            reader.Read();
        }

        return result;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        throw new NotSupportedException();
    }
}

This is a concrete implementation, and absolutely can be implemented more extended and useful. But it just solve my current problem.

Solution 2

So your input Dictionary contains long (based on discussion in comments).

The easiest fix is to add Convert.ChangeType before SetValue.
(passing in sourceValue and Constant(map.Field.FieldType))
However, this may have an unintended consequence of allowing string -> int conversion.

Alternative is to add your own ConvertType method, where you decide how types are converted.

Share:
15,861
amiry jd
Author by

amiry jd

My other SO-Profile: https://stackoverflow.com/users/974276/j-amiry I am a hard-working and innovative developer with near 18 years of experience mostly based on .NET (Framework &amp; Core) technology stack. For the last few years, alongside developing some awesome applications, I was focused on some non-coding tasks such as building up development teams, team leading, tech leading, problem solving, consulting, architecting, reviewing other's code, and mentoring. As a self-motivated fast-learner experienced code-guy, who has a proactive personality to step up to new challenges, I think I'm the man who can get the job done.

Updated on June 15, 2022

Comments

  • amiry jd
    amiry jd almost 2 years

    I asked a question yesterday here about reading properties from an anonymous object and writing them to private fields of a class. The problem solved. Here is the short story:

    I have some data in json format. I deserialize them to ExpandoObject, and pass them as IDictionary<string, object> to method. It works fine, except Int32 properties. It seems they change to Int64, where? I don't know.

    Here is the method again:

        private Func<IDictionary<string, object>, dynamic> MakeCreator(
            Type type, Expression ctor,
            IEnumerable<PropertyToFieldMapper> maps) {
    
            var list = new List<Expression>();
            var vList = new List<ParameterExpression>();
    
            // creating new target
            var targetVariable = Expression.Variable(type, "targetVariable");
            vList.Add(targetVariable);
            list.Add(Expression.Assign(targetVariable, Expression.Convert(ctor, type)));
    
            // accessing source
            var sourceType = typeof(IDictionary<string, object>);
            var sourceParameter = Expression.Parameter(sourceType, "sourceParameter");
    
            // calling source ContainsKey(string) method
            var containsKeyMethodInfo = sourceType.GetMethod("ContainsKey", new[] { typeof(string) });
    
            var accessSourceIndexerProp = sourceType.GetProperty("Item");
            var accessSourceIndexerInfo = accessSourceIndexerProp.GetGetMethod();
    
            // itrate over writers and add their Call to block
            var containsKeyMethodArgument = Expression.Variable(typeof(string), "containsKeyMethodArgument");
            vList.Add(containsKeyMethodArgument);
            foreach (var map in maps) {
                list.Add(Expression.Assign(containsKeyMethodArgument, Expression.Constant(map.Property.Name)));
                var containsKeyMethodCall = Expression.Call(sourceParameter, containsKeyMethodInfo,
                                                            new Expression[] { containsKeyMethodArgument });
    
                // creating writer
                var sourceValue = Expression.Call(sourceParameter, accessSourceIndexerInfo,
                                                  new Expression[] { containsKeyMethodArgument });
                var setterInfo = map.Field.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });
                var setterCall = Expression.Call(Expression.Constant(map.Field), setterInfo,
                    new Expression[] {
                                         Expression.Convert(targetVariable, typeof(object)),
                                         Expression.Convert(sourceValue, typeof(object))
                                     });
                Console.WriteLine(Expression.Lambda(setterCall));
                list.Add(Expression.IfThen(containsKeyMethodCall, setterCall));
            }
            list.Add(targetVariable);
    
            var block = Expression.Block(vList, list);
    
            var lambda = Expression.Lambda<Func<IDictionary<string, object>, dynamic>>(
                block, new[] { sourceParameter }
                );
    
            return lambda.Compile();
        }
    

    If we have this

    public class Person {
        public int Age { get; set; }
        public string Name { get; set; }
    }
    

    class, and use this object

    var data = new { Name = "Amiry", Age = 20 };
    

    to initialize an instance of Person using above method, this error occurs:

    Object of type 'System.Int64' cannot be converted to type 'System.Int32'.

    But if we change Age property to:

    public long Age { get; set; }
    

    every thing looks fine and method works perfectly. I completely confused about why this happens. Do you have any idea?

  • amiry jd
    amiry jd almost 11 years
    +1 to idea. But it is not as simple as you say :D I'm in a expression tree, and I should create some expressions to call Convert.ChangeType :( It seems creating a custom JsonConvertor is more easier. However, thanks for the idea. Cheers.
  • Mike
    Mike over 6 years
    This worked great for me. The one change I would suggest is to replace: return objectType == typeof(IDictionary<string, object>); with return typeof(IDictionary<string, object>).IsAssignableFrom(objectType); That way it works for any dictionary type.
  • AshleyS
    AshleyS about 4 years
    This solution doesn't deserialize the data recursively unfortunately. After handling integers, I check if reader.TokenType == JsonToken.StartObject and then deserialize it recursively with value = serializer.Deserialize<IDictionary<string, object>>(reader);