通过反射调用NewtonSoft SerializeObject和DeserializeObject方法以解决版本不匹配的问题

时间:2018-08-31 22:40:41

标签: json.net

我们有一个客户端DLL,其中具有针对NewtonSoft版本9构建的简单JSON帮助程序,以帮助进行序列化和反序列化。我们想在另一个项目(由一个合作伙伴团队拥有)中使用此DLL,但该团队全面使用NewtonSoft ver.6。由于NewtonSoft绑定的版本不同,导致生成错误。而且,将使用项目升级到版本9超出了范围。

有人针对这种情况有解决方案吗?

我试图使JSON包装器在构建时不显式链接,而是在运行时使用Reflection调用NewtonSoft方法。

这是实现此功能的我的DelayLoadedJsonHelper代码:

namespace Test
{
using System;
using System.IO;
using System.Linq;
using System.Reflection;

/// <summary>
/// Helper class for serializing and deserializing JSON using a runtime binding to the NewtonSoft Json binary
/// shipped along with our package.
/// This allows the our code to use a specific version of NewtonSoft irrespective of the NewtonSoft
/// version that is being consumed by the code consuming the our package.
/// The NewtonSoft binary is shipped with our package in a sub-folder named PrivateNewtonSoft and is explicitly
/// loaded from that sub-folder from the current directory of the executing process.
/// </summary>
public class DelayLoadedJsonHelper
{                                       
    private static readonly string SubFolderName = "PrivateNewtonSoft";
    private static readonly string NewtonSoftJsonDllName = "NewtonSoft.Json.Dll";

    private static Assembly TargetNewtonSoftAssembly;
    private static object CustomJsonSerializerSettings = null;
    private static MethodInfo SerializeObjectMethod = null;
    private static MethodInfo DeserializeObjectMethod = null;

    /// <summary>
    /// Static constructor to initialize the delay loaded NewtonSoft dependencies.
    /// </summary>
    static DelayLoadedJsonHelper()
    {
        // Load the private version of the NewtonSoft binary
        // ------------------------------------------------
        string currentDirectory = Directory.GetCurrentDirectory();
        string targetAssemblyPath = Path.Combine(currentDirectory, SubFolderName, NewtonSoftJsonDllName);
        DelayLoadedJsonHelper.TargetNewtonSoftAssembly = Assembly.LoadFrom(targetAssemblyPath);

        // Get the enum value for NullValueHandling.Ignore
        // ------------------------------------------------
        Type nullValueHandlingType = DelayLoadedJsonHelper.TargetNewtonSoftAssembly.GetType("Newtonsoft.Json.NullValueHandling",
                                                                                            throwOnError: true,
                                                                                            ignoreCase: true);
        var nullValueHandlingIgnore = Enum.Parse(nullValueHandlingType, "Ignore");

        // Get the enum value for Formatting.None
        // ------------------------------------------------
        Type formattingType = DelayLoadedJsonHelper.TargetNewtonSoftAssembly.GetType("Newtonsoft.Json.Formatting",
                                                                                     throwOnError: true,
                                                                                     ignoreCase: true);
        var formattingNone = Enum.Parse(formattingType, "None");

        // Instantiate the DefaultContractResolver object
        // ------------------------------------------------
        Type defaultContractResolverType = DelayLoadedJsonHelper.TargetNewtonSoftAssembly.GetType("Newtonsoft.Json.Serialization.DefaultContractResolver",
                                                                                     throwOnError: true,
                                                                                     ignoreCase: true);
        var defaultContractResolverInstance = Activator.CreateInstance(defaultContractResolverType);

        // Generate the custom JsonSerializerSettings object and set the properties.
        // This custom JsonSerializerSettings will be used when invoking the SerializeObject
        // and DeserializeObject methods.
        // -----------------------------------------------------------------------------
        Type jsonSerializerSettingsType = DelayLoadedJsonHelper.TargetNewtonSoftAssembly.GetType("Newtonsoft.Json.JsonSerializerSettings",
                                                                         throwOnError: true,
                                                                         ignoreCase: true);

        DelayLoadedJsonHelper.CustomJsonSerializerSettings = Activator.CreateInstance(jsonSerializerSettingsType);

        jsonSerializerSettingsType.GetProperty("NullValueHandling").SetValue(DelayLoadedJsonHelper.CustomJsonSerializerSettings,
                                                                             nullValueHandlingIgnore,
                                                                             null);

        jsonSerializerSettingsType.GetProperty("Formatting").SetValue(DelayLoadedJsonHelper.CustomJsonSerializerSettings,
                                                                      formattingNone,
                                                                      null);

        jsonSerializerSettingsType.GetProperty("ContractResolver").SetValue(DelayLoadedJsonHelper.CustomJsonSerializerSettings,
                                                                            defaultContractResolverInstance,
                                                                            null);

        // Get the SerializeObject and DeserializeObject methods
        // -----------------------------------------------------
        Type jsonConvert = DelayLoadedJsonHelper.TargetNewtonSoftAssembly.GetType("Newtonsoft.Json.JsonConvert",
                                                                                  throwOnError: true,
                                                                                  ignoreCase: true);

        DelayLoadedJsonHelper.SerializeObjectMethod = jsonConvert.GetMethods()
                                                                 .Single(m => string.Equals(m.Name, "SerializeObject")
                                                                         && m.GetParameters().Length == 2
                                                                         && m.GetParameters()[0].ParameterType == typeof(object)
                                                                         && m.GetParameters()[1].ParameterType == jsonSerializerSettingsType);

        DelayLoadedJsonHelper.DeserializeObjectMethod = jsonConvert.GetMethods()
                                                                   .Single(m => string.Equals(m.Name, "DeserializeObject")
                                                                           && m.IsGenericMethodDefinition
                                                                           && m.GetParameters().Length == 2
                                                                           && m.GetParameters()[0].ParameterType == typeof(string)
                                                                           && m.GetParameters()[1].ParameterType == jsonSerializerSettingsType);
    }

    /// <summary>
    /// Serialize an object to a json string
    /// </summary>
    /// <param name="obj">the object to serialize</param>
    /// <returns>json string</returns>
    public static string SerializeObject(object obj)
    {
        return DelayLoadedJsonHelper.SerializeObjectMethod.Invoke(null, 
                                                                  new object[] {
                                                                                obj,
                                                                                DelayLoadedJsonHelper.CustomJsonSerializerSettings
                                                                               })
                                                          .ToString();
    }

    /// <summary>
    /// Deserialize a json string to C# object
    /// </summary>
    /// <typeparam name="T">the type of the object</typeparam>
    /// <param name="json">the json string</param>
    /// <returns>an object of type T</returns>
    public static T DeserializeObject<T>(string json)
    {
        var genericMethod = DelayLoadedJsonHelper.DeserializeObjectMethod.MakeGenericMethod(new Type[] { typeof(T) });
        return (T) genericMethod.Invoke(null,
                                        new object[] {
                                                        json,
                                                        DelayLoadedJsonHelper.CustomJsonSerializerSettings
                                                     });
    }
}
}

这对于所有标准序列化和反序列化都很好。

但是,其中一个消耗项目是使用Microsoft.Azure.KeyVault REST API。而且,此DelayLoadedJsonHelper无法反序列化序列化的Secret数据类型。

但是,如果我直接针对NewtonSoft进行编译并在REST API响应上调用JsonConvert.DeserializeObject,它就可以正常工作。

这里是显示错误的快速单元测试实现。注意,您必须链接到Secret类的Microsoft.Azure.KeyVault.dll。我们目前正在使用此nuget pkg的1.0.0版本。

[TestMethod]
public void DeserializeKeyVaultResponse()
{
    string testJson = "{\"value\":\"_be07-4342-afa6-46696fe3c69c\",\"id\":\"https://test-vault.vault.azure.net/secrets/sqlLogin-test3-Password2/003d384c\",\"attributes\":{\"enabled\":true,\"created\":1447207748,\"updated\":1447207748},\"tags\":{\"Name\":\"Test3\",\"Param1\":\"para1: 62df9b2b-089e-41a8-acff-d05dcec08ad4\",\"Param2\":\"para2: 082d8fa2-14ca-444f-b87d-5564b1f6a4db\",\"Param3\":\"para3: f88df110-543a-45a7-8b44-991e3d859386\",\"Param4\":\"para4: d9d5e3b6-250f-4ab6-8aea-a501004bc95e\",\"Param5\":\"para5: a1065260-a8d2-4001-945e-db0c8830b749\"}}";
    var deserialized = DelayLoadedJsonHelper.DeserializeObject<Secret>(testJson);
    Assert.IsNotNull(deserialized);
}

此操作失败,并显示以下错误:

消息:测试方法DelayLoadedJsonHelperTests.DeserializeKeyVaultResponse引发了异常: System.Reflection.TargetInvocationException:调用的目标引发了异常。 ---> Newtonsoft.Json.JsonReaderException:解析值时遇到意外字符:1.路径'attributes.created',第1行,位置164。

直接使用NewtonSoft JsonConvert方法(即直接针对NewtonSoft进行编译)时,同一测试成功通过:

[TestMethod]
public void DeserializeKeyVaultResponseUsingNewtonSoftDirectly()
{
    // Deserialize directly using compiled NewtonSoft.
    // These are the same settings used by custom SerailizerSettings
    // in DelayLoadedJsonHelper
    JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
    {
        NullValueHandling = NullValueHandling.Ignore,
        Formatting = Formatting.None,
        ContractResolver = new DefaultContractResolver(),
    };

    string testJson = "{\"value\":\"_be07-4342-afa6-46696fe3c69c\",\"id\":\"https://test-vault.vault.azure.net/secrets/sqlLogin-test3-Password2/003d384c\",\"attributes\":{\"enabled\":true,\"created\":1447207748,\"updated\":1447207748},\"tags\":{\"Name\":\"Test3\",\"Param1\":\"para1: 62df9b2b-089e-41a8-acff-d05dcec08ad4\",\"Param2\":\"para2: 082d8fa2-14ca-444f-b87d-5564b1f6a4db\",\"Param3\":\"para3: f88df110-543a-45a7-8b44-991e3d859386\",\"Param4\":\"para4: d9d5e3b6-250f-4ab6-8aea-a501004bc95e\",\"Param5\":\"para5: a1065260-a8d2-4001-945e-db0c8830b749\"}}";
    var deserialized = JsonConvert.DeserializeObject<Secret>(testJson);
    Assert.IsNotNull(deserialized);
}

问题似乎是 Microsoft.Azure.KeyVault.Secret 对象具有Unix时代的自定义DateTime实现以及在Json中的表示方式。您可以查看针对Microsoft.Azure.KeyVault软件包1.0.0版使用ILSPY的详细信息(可在此处找到:https://www.nuget.org/packages/Microsoft.Azure.KeyVault/

我很困惑为什么在编译的调用有效的情况下,通过反射反序列化调用失败的原因。我怀疑我的DelayLoadedJsonHelper缺少其他初始化。

有人对我如何解决此问题有想法吗? 谢谢!

0 个答案:

没有答案