-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug in Json.Net: System.Int32 is deserialized as System.Int64 #1269
Comments
I suspect it is the same issue we had with Guid with Json serializer: #1187 (comment) and JamesNK/Newtonsoft.Json#762 and JamesNK/Newtonsoft.Json#728 (comment). This is, as far as I am concerned, a bug in Json.Net. They consider it a feature. Essentially, they loose type info for primitives and some known types, like |
Gabi thanks, I look briefly into the other issue and understand the problem, but other than taking a external serializer, is there a way to decorate the pocos? |
Try long instead of int in your poco. |
There are additional gotcha in this area that I discovered when deserializing using Json.NET with weak typing (i.e. when Json.NET doesn't have specific type information during deserialization):
Due to all this, I've written my own conversion code to fix any loss of type information when converting to JSON, included below. It's not perfect, and it evolves as I discover new corner cases, but it addresses some of the oddities you might encounter. public static class JsonHelper
{
private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };
/// <summary>
/// Converts values that were deserialized from JSON with weak typing (e.g. into <see cref="object"/>) back into
/// their strong type, according to the specified target type.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="targetType">The type the value should be converted into.</param>
/// <returns>The converted value.</returns>
public static object ConvertWeaklyTypedValue(object value, Type targetType)
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
if (value == null)
return null;
if (targetType.IsInstanceOfType(value))
return value;
var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (paramType.IsEnum)
{
if (value is string)
return Enum.Parse(paramType, (string)value);
else
return Enum.ToObject(paramType, value);
}
if (paramType == typeof(Guid))
{
return Guid.Parse((string)value);
}
if (_specialNumericTypes.Contains(paramType))
{
if (value is BigInteger)
return (ulong)(BigInteger)value;
else
return Convert.ChangeType(value, paramType);
}
if (value is long)
{
return Convert.ChangeType(value, paramType);
}
throw new ArgumentException($"Cannot convert a value of type {value.GetType()} to {targetType}.");
}
} |
Thank you Alon! |
Alon, The variable that broke for me was a bool. _Nice if you could add also a condition to parse true/false._ Yet it is confusing because there are other bool properties in the state that I am serializing and there is no exception. This is the exception that crashed on type Boolean: Data #3 Key=DisableAlarms Value=False Type=System.Boolean |
Gabi, Alon, My 2 cents: I did a simple test bypassing the internal state management, it will require a lot of refactoring, but pass the template to this function:
or somewhere in the call pass the type, then the Json corner cases disappear. |
@gabikliot: I don't hook it up to Json.NET itself. Since I mostly deserialize into strongly-typed classes, and only in a few specific areas I deal with weakly typed data (such as something that comes from reflection), I simply call this method for every value that was weakly typed. Hooking up to Json.NET could incur a performance penalty for strongly typed data that doesn't need such 'fixups'. @ukluk: I'm not sure how it could break for a bool, since it has a unique representation in JSON, therefore you never lose any type information. Could you show an example? Also, while all these issues can indeed be avoided if you deserialize into a strongly typed object (which I think is what Orleans should aspire to do) sometimes you don't have the type information to that, or its too complicated to do it (e.g. runtime codegen). |
@Allon-Guralnek I included the exception, but here the bomb (Data #3): Data #3 Key=DisableAlarms Value=False Type=System.Boolean |
@ukluk , can you try to define that class without extending |
Thank you @Allon-Guralnek . I had exactly the same impersonation, after playing with Json.Net, like you wrote:
Basically, it is almost impossible, as far as I can tell, to write a fully 100% compatible Json.Net serialier that will take ANY dynamic type and will correctly deserialize it back. Json.Net simply looses some dynamic type info. For example, for Now, I was inaccurately saying it is not possible. It is possible, if one writes basically his full Converter to correctly handle ALL types in all situations. Basically, you end up building a "serializer within the serializer" - Json.Net will be calling your methods and you will be dealing with emitting the right type info into strings and on the reverse path it will be your code to correctly reconstruct the type back. Basically, for us, Orleans, that would look pretty similar to our binary serializer that we have now, just emitting strings. I don't think we want to go that route, at least not in the near future. Instead, what we should try to do, as much as we can, is what you both wrote: Or maybe there is another way around it. |
@ukluk: Sorry, I still don't understand the issue. I'm talking pure Json.NET, not Orleans. @gabikliot: There is no need to pump In my case, I have an RPC mechanism over JSON and I need to serialize the arguments to a method. When it serializes an What I would love to so is at compile time generate a class per method that will have a strongly-typed property for each parameter of a given method, then fill and serialize that class, and not an I cannot do that since I don't have a good code generation infrastructure. Orleans of the other hand, does, and should do that. You shouldn't use a If you do that, you will never encounter any of Json.NET's serialization oddities. |
@Allon-Guralnek, ohhh, maybe we need to try The full solution is yes - reuse Orleans code gen, to code gen fully correct Json serializer (to solve the |
Maybe @ReubenBond wants to chime in here, w.r.t. to @Allon-Guralnek 's suggestion on using Orleans code gen to solve "Json.NET's serialization oddities". I should not be saying so categorically that "we don't want to spend time on that". I know I don't. But maybe other do. |
@gabikliot: Json.NET uses a wire format it has no control over, therefore it works for JSON-compatible data types or when it has type information. When you use types that are represented differently in JSON and also you don't give it any type information, only then it falters. I don't think I'd call that 'broken' as it never failed to provide what it claims. You simply have more advanced requirements, but then you can consider Orleans serializes to be "broken" since they don't have a widely-compatible and universally-readable wire format (again, not really broken since it was never a requirement Orleans serializers tried to fulfill). |
Thanks for explaining that Allon. I did not know it was because of the standard wire compatibility requirements. Makes sense now. |
As was discussed here and in other related issues, this is not a bug in Orleans, but is rather the way Json serializer works. Closing this issue. |
My state object produces errors during serialization in the Boolean object. Do I need to update to 1.1.1?
This is the object:
and this is the error:
Data #1 Key=Model Value=Mesh.Ratatouille.Model.Site.AsSite Type=Mesh.Ratatouille.Model.Site.AsSite
Data #2 Key=Threshold Value=0 Type=System.Int64
Data #3 Key=DisableAlarms Value=False Type=System.Boolean
at Orleans.Storage.AzureTableStorage.ConvertFromStorageFormat(GrainState grainState, GrainStateEntity entity)
at Orleans.Storage.AzureTableStorage.d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Orleans.Runtime.Scheduler.SchedulerExtensions.<>c__DisplayClassa.<b__8>d__c.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Orleans.Runtime.Catalog.d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Orleans.Runtime.Catalog.d__2b.MoveNext()
Exc level 1: System.ArgumentException: Object of type 'System.Int64' cannot be converted to type 'System.Int32'.
at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at Orleans.GrainState.SetAll(IDictionary`2 values)
at Orleans.Storage.AzureTableStorage.ConvertFromStorageFormat(GrainState grainState, GrainStateEntity entity)
The text was updated successfully, but these errors were encountered: