3

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.

4
  • You most likely have two copies of the dll loaded. Please check out the app service editor and add a description of the function app file system structure from there. Are you doing any assembly loading yourself? Commented Aug 1, 2017 at 18:19
  • Also are you using csx or a precompiled function dll? Commented Aug 1, 2017 at 18:20
  • @Jeff I am using csx. I had been loading my own assemblies, but removed the file and the import after adding the project.json file. I am now getting a different error (updated above). Commented Aug 1, 2017 at 18:33
  • Makes more sense now. Use the below answer and it will resolve. Commented Aug 1, 2017 at 18:35

2 Answers 2

3

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);
    }
}

`

Sign up to request clarification or add additional context in comments.

Comments

0

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));
                            }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.