Reputation: 187
I'm running into a strange issue with Azure Function Apps. Newtonsoft Json.NET deserialization is not liking the $type
annotations. My deserialization code looks like:
return JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto
});
The json looks like:
{
"$type": "Trading.Control.Json.TradingConfig, Trading",
"Config": {
"$type": "Trading.Control.Json.Config, Trading",
"Optimize": false
},
"Trading": {
"$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[Trading.Platforms.Credentials, Trading]], mscorlib",
...
And is serialized with:
return JsonConvert.SerializeObject(o, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All,
Formatting = Formatting.Indented
});
The error is:
2017-08-01T17:32:46.395 Type specified in JSON
'Trading.Control.Json.TradingConfig, Trading, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with
'Trading.Control.Json.TradingConfig, Trading, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Path '$type', line 2, position 56.
As you can see, the types appear to be identical. This code is well tested locally and works as expected. It will fail in Azure on the first $type
annotation encountered, regardless of how many I remove.
I would like to keep using the annotations, as I need them for deserializing objects derived from an abstract class.
This is compiled in x64, .NET 4.7, Json.NET v10.0.3, Azure Function Apps v1.0.11027.0 (~1). I have the Newtonsoft.Json.dll file in the bin folder, with #r "Newtonsoft.Json.dll"
to reference it. Any ideas? Much appreciated.
Edit: I have also tried adding a project.json file looking like:
{
"frameworks": {
"net47":{
"dependencies": {
"Newtonsoft.Json": "10.0.3"
}
}
}
}
which successfully installed. I removed the assembly file I uploaded, and the #r
import. The error is now:
2017-08-01T18:30:18.971 Error resolving type specified in JSON 'Trading.Control.Json.TradingConfig, Trading'. Path '$type', line 2, position 56.
I suspect there is a "base namespace" or somesuch lookup error.
The filesystem of the function looks like: /site/wwwroot/TimerTriggerCSharp3/
with assemblies in a bin folder. They are all loaded with #r
imports, which work fine.
Upvotes: 3
Views: 3466
Reputation: 181
Had to change this part to make it work
if (num1 == 0)
{
var typeArgumentName = SplitFullyQualifiedTypeName(typeName.Substring(startIndex, index - startIndex));
typeList.Add(Type.GetType(typeArgumentName));
}
Upvotes: 0
Reputation: 36573
I had the same problem and resolved it using a SerializationBinder. It happens because of the load context of assemblies loaded by the function runtime
The below code let's you take an arbitrary assembly name and resolve it. You can pass it in with the serialization settings. So you could check for the trading assembly. The the comment on the class for why it's necessary
a => a.GetName().Name == "Trading" ? typeof(Trading.Control.Json.TradingConfig).Assembly : null;
/// <summary>
/// Uses the func to resolve assembly instances by name, since they may be in a different directory and not
/// directly resolvable by Assembly.Load (the default method used by JSON.NET)
/// </summary>
internal class SerializationBinder : DefaultSerializationBinder
{
private readonly Func<string, Assembly> assemblyResolver;
public SerializationBinder(Func<string, Assembly> assemblyResolver)
{
this.assemblyResolver = assemblyResolver;
}
public override Type BindToType(string assemblyName, string typeName)
{
Assembly assembly;
try
{
assembly = assemblyResolver(assemblyName);
}
catch
{
// not registered
return base.BindToType(assemblyName, typeName);
}
var type = assembly.GetType(typeName);
if (type == null)
type = GetGenericTypeFromTypeName(typeName, assembly);
if (type != null) return type;
return base.BindToType(assemblyName, typeName);
}
/// <summary>
/// From DefaultSerializationBinder.
/// </summary>
/// <param name="typeName"></param>
/// <param name="assembly"></param>
/// <returns></returns>
private Type GetGenericTypeFromTypeName(string typeName, Assembly assembly)
{
Type type1 = null;
var length = typeName.IndexOf('[');
if (length >= 0)
{
var name = typeName.Substring(0, length);
var type2 = assembly.GetType(name);
if (type2 != null)
{
var typeList = new List<Type>();
var num1 = 0;
var startIndex = 0;
var num2 = typeName.Length - 1;
for (var index = length + 1; index < num2; ++index)
switch (typeName[index])
{
case '[':
if (num1 == 0)
startIndex = index + 1;
++num1;
break;
case ']':
--num1;
if (num1 == 0)
{
typeName = SplitFullyQualifiedTypeName(typeName.Substring(startIndex, index - startIndex));
return Type.GetType(typeName);
}
break;
}
type1 = type2.MakeGenericType(typeList.ToArray());
}
}
return type1;
}
/// <summary>
/// From Reflectionutils
/// </summary>
/// <param name="fullyQualifiedTypeName"></param>
/// <returns></returns>
private static string SplitFullyQualifiedTypeName(string fullyQualifiedTypeName)
{
var assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
string typeName;
if (assemblyDelimiterIndex.HasValue)
typeName = Trim(fullyQualifiedTypeName, 0, assemblyDelimiterIndex.GetValueOrDefault());
else
typeName = fullyQualifiedTypeName;
return typeName;
}
/// <summary>
/// From Reflectionutils
/// </summary>
/// <param name="fullyQualifiedTypeName"></param>
/// <returns></returns>
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
{
var num = 0;
for (var index = 0; index < fullyQualifiedTypeName.Length; ++index)
switch (fullyQualifiedTypeName[index])
{
case ',':
if (num == 0)
return index;
break;
case '[':
++num;
break;
case ']':
--num;
break;
}
return new int?();
}
private static string Trim(string s, int start, int length)
{
if (s == null)
throw new ArgumentNullException();
if (start < 0)
throw new ArgumentOutOfRangeException("start");
if (length < 0)
throw new ArgumentOutOfRangeException("length");
var index = start + length - 1;
if (index >= s.Length)
throw new ArgumentOutOfRangeException("length");
while (start < index && char.IsWhiteSpace(s[start]))
++start;
while (index >= start && char.IsWhiteSpace(s[index]))
--index;
return s.Substring(start, index - start + 1);
}
}
`
Upvotes: 3