我们有一个客户端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缺少其他初始化。
有人对我如何解决此问题有想法吗? 谢谢!