如何使用 Newtonsoft.Json 序列化 castle 动态代理接口

How to serialize a castle dynamic proxy interface using Newtonsoft.Json

提问人:Philip Atz 提问时间:10/5/2023 更新时间:10/5/2023 访问量:66

问:

我的应用程序接口有许多代理(我们称之为 ),它们是使用 Castle DynamicProxy 生成的,并填充了所有正确的值。代理对象的类型为 。当我尝试使用 Newtonsoft.Json 将这些代理序列化为 JSON 时,我希望得到一个与为以下定义定义的属性匹配的 JSON:IConfigCastle.Proxies.IConfigProxyIConfig

var json = JsonConvert.SerializeObject(proxy);

但是,我得到的是一个字符串,其中包含代理的内部工作原理,并且没有包含在以下数据中:IConfig

{
    "__interceptors": [{}],
    "__target": null,
    "__interfaces": [],
    "__baseType": "System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
    "__proxyGenerationOptions": {
        "hook": {},
        "selector": null,
        "mixins": null,
        "baseTypeForInterfaceProxy.AssemblyQualifiedName": "System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
    },
    "__proxyTypeId": "interface.without.target",
    "__targetFieldType": "System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
    "__theInterface": "MyNamespace.IConfig, MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
}

我遇到的最初问题是使用 Config.Net NuGet 包生成的动态代理。但是,我现在使用 Moq 在 LINQPad 中重新创建了一个最小的可重现样本。下面是一些生成类似结果的示例代码:

void Main()
{
    var config = new Mock<IConfig>();
    config.Setup(x => x.Key).Returns("test");
    var proxy = config.Object;
    var json = JsonConvert.SerializeObject(proxy, Newtonsoft.Json.Formatting.Indented);
    json.Dump();
}

// Define other methods and classes here
public interface IConfig
{
    string Key { get; set; }
}

对于这个特定问题,我将 .NET Framework 4.8 与最新版本的 Json.NET 一起使用(对于示例代码 Moq)。

有人可以帮忙解释这种行为并告诉我一种将这些对象转换为 JSON 的方法吗?

C# JSON json.net castle-dynamicproxy

评论

1赞 dbc 10/5/2023
只是出于好奇,如果使用 System.Text.Json 序列化会发生什么?
0赞 Philip Atz 10/5/2023
这实际上有效,谢谢!请随时提出答案!所以我想这是 Json.NET 读取代理对象的方式存在问题。
1赞 dbc 10/5/2023
System.Text.Json 仅序列化该特定引用的声明属性,除非声明为 .我敢打赌,如果你这样做了,那么System.Text.Json的行为会像 Json.NET 一样。但 Json.NET 序列化对象本身的实际属性,而不是引用的声明属性。objectJsonSerializer.Serialize((object)proxy)

答:

1赞 dbc 10/5/2023 #1

问题的原因是,当您将声明类型的引用序列化为具有某种具体类型的对象时,Json.NET:IConfig

JsonConvert.SerializeObject<IConfig>(IConfig proxy);

Json.NET 将通过调用 来确定对象的实际具体类型,然后序列化该具体类型的所有属性 -- 并且可能比引用的声明类型多得多。(即声明的类型实际上没有使用)。proxy.GetType()IConfig

若要对此进行调试,可以使用 Json.NET 的 DefaultContractResolver 来转储实际将序列化的属性:

var contract = (new DefaultContractResolver()).ResolveContract(proxy.GetType());
Console.WriteLine(proxy.GetType());
if (contract is JsonObjectContract objContract)
    foreach (var property in objContract.Properties)
        Console.WriteLine("  {0}: {1}", property.UnderlyingName, property.PropertyType);

这导致:

Castle.Proxies.IConfigProxy
  Key: System.String
  Mock: Moq.Mock
  Moq.IMocked`1[IConfig].Mock: Moq.Mock`1[IConfig]
  Interceptor: System.Object

正是这些额外的属性导致了问题。

在这里演示小提琴 #1。

作为解决方法,您可以切换到内置序列化程序 System.Text.Json,它仅序列化引用的声明类型的属性,除非声明的类型为 [1]object

var json = System.Text.Json.JsonSerializer.Serialize<IConfig>(
            proxy, 
            new System.Text.Json.JsonSerializerOptions { WriteIndented = true });

在这里演示小提琴 #2。

或者,您可以使用“仅将接口属性序列化为 JSON Json.net”中的答案之一进行调查,以序列化代理 - 但使用内置序列化程序似乎更简单。


[1] 若要确认,请参阅如何使用 System.Text.Json 序列化派生类的属性