Expression.Convert: Object of type 'System.Int64' cannot be converted to type 'System.Int32'
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.
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 & 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, 2022Comments
-
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 asIDictionary<string, object>
to method. It works fine, exceptInt32
properties. It seems they change toInt64
, 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 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 customJsonConvertor
is more easier. However, thanks for the idea. Cheers. -
Mike over 6 yearsThis worked great for me. The one change I would suggest is to replace:
return objectType == typeof(IDictionary<string, object>);
withreturn typeof(IDictionary<string, object>).IsAssignableFrom(objectType);
That way it works for any dictionary type. -
AshleyS about 4 yearsThis solution doesn't deserialize the data recursively unfortunately. After handling integers, I check if
reader.TokenType == JsonToken.StartObject
and then deserialize it recursively withvalue = serializer.Deserialize<IDictionary<string, object>>(reader);