I have a number of proxies for my application's interface (let's call it IConfig
) that were generated using Castle DynamicProxy and populated with all the right values. The proxy objects are of type Castle.Proxies.IConfigProxy
. When I try to serialise these proxies to JSON, using Newtonsoft.Json, I expect to get a JSON matching the properties defined for an IConfig
:
var json = JsonConvert.SerializeObject(proxy);
However, I instead get a string with the internal workings of the proxy and none of the data contained in the 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"
}
The original issue I encountered was with dynamic proxies generated using the Config.Net NuGet package. However, I have now recreated a minimal reproducible sample in LINQPad using Moq. Here is some sample code that generates a similar result:
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; }
}
For this particular issue I am using .NET Framework 4.8 with the latest versions of Json.NET (and in the case of the sample code Moq).
Can someone please help explain this behaviour and show me a way to translate these objects to JSON?
The cause of your problem is that, when you serialize a reference of declared type IConfig
to an object of some concrete type with Json.NET:
JsonConvert.SerializeObject<IConfig>(IConfig proxy);
Json.NET will determine the actual, concrete type of the object by calling proxy.GetType()
, and then serialize all the properties of that concrete type -- and there may be many more than for the declared type of the reference. (I.e. the declared type IConfig
is not actually used).
To debug this, you may use Json.NET's DefaultContractResolver
to dump the properties that will actually get serialized:
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);
This results in:
Castle.Proxies.IConfigProxy
Key: System.String
Mock: Moq.Mock
Moq.IMocked`1[IConfig].Mock: Moq.Mock`1[IConfig]
Interceptor: System.Object
It's those extra properties that are causing the problem.
Demo fiddle #1 here.
As a workaround you could switch to the builtin serializer System.Text.Json, which does serialize only the properties of the declared type of the reference, unless the declared type is object
[1]:
var json = System.Text.Json.JsonSerializer.Serialize<IConfig>(
proxy,
new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
Demo fiddle #2 here.
Alternatively, you could investigate using one of the answers from Serialize only interface properties to JSON with Json.net to serialize your proxy -- but using the builtin serializer seems simpler.
[1] For confirmation see How to serialize properties of derived classes with System.Text.Json.