问题
I have an object with a list of base class sub-objects. Sub-objects need a custom converter. I can't make my custom converter respect ItemTypeNameHandling
option.
Sample code (create a new C# Console project, add JSON.NET NuGet package):
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace My {
class Program {
private static void Main () {
Console.WriteLine(JsonConvert.SerializeObject(
new Box { toys = { new Spintop(), new Ball() } },
Formatting.Indented));
Console.ReadKey();
}
}
[JsonObject] class Box
{
[JsonProperty (
ItemConverterType = typeof(ToyConverter),
ItemTypeNameHandling = TypeNameHandling.Auto)]
public List<Toy> toys = new List<Toy>();
}
[JsonObject] class Toy {}
[JsonObject] class Spintop : Toy {}
[JsonObject] class Ball : Toy {}
class ToyConverter : JsonConverter {
public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) {
serializer.Serialize(writer, value);
}
public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
return serializer.Deserialize(reader, objectType);
}
public override bool CanConvert (Type objectType) {
return typeof(Toy).IsAssignableFrom(objectType);
}
}
}
Produced output:
{
"toys": [
{},
{}
]
}
Necessary output (this is what happens if I comment ItemConverterType = typeof(ToyConverter),
line):
{
"toys": [
{
"$type": "My.Spintop, Serialization"
},
{
"$type": "My.Ball, Serialization"
}
]
}
I've tried temporarily changing value of serializer.TypeNameHandling
in ToyConverter.WriteJson
method, but it affects unrelated properties. (Of course, my real converter is more complex than that. It's just an example with base functionality.)
Question: How to make my custom JsonConverter
respect ItemTypeNameHandling
property of JsonProperty
attribute?
回答1:
Having delved into the source code for Json.Net (version 4.5 release 11), it looks as though what you want to do is not possible.
The key to getting types written to the output is this method:
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter
.ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract,
JsonProperty member, JsonContainerContract containerContract,
JsonProperty containerProperty)
It's the containerContract
and containerProperty
parameters that are important here. When serializing without a converter, these parameters are supplied and ShouldWriteType
is able to use them to figure out what TypeNameHandling
to use.
When serializing with a converter, however, those two parameters are not supplied. This appears to be because ToyConverter.WriteJson
results in a call to Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue
like this:
SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null);
Note that the last two parameters are in fact a JsonContainerContract containerContract
and JsonProperty containerProperty
, and are passed down the chain to the ShouldWriteType
method. Therein lies the problem: because they are null, the logic of the ShouldWriteType
method means that it returns false
, thus the type is not written.
Edit:
Inspired by this, you could workaround this problem by customising the WriteJson
method of your converter:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("$type");
writer.WriteValue(RemoveAssemblyDetails(value.GetType().AssemblyQualifiedName.ToString()));
writer.WriteEndObject();
}
private static string RemoveAssemblyDetails(string fullyQualifiedTypeName)
{
StringBuilder builder = new StringBuilder();
// loop through the type name and filter out qualified assembly details from nested type names
bool writingAssemblyName = false;
bool skippingAssemblyDetails = false;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
writingAssemblyName = false;
skippingAssemblyDetails = false;
builder.Append(current);
break;
case ']':
writingAssemblyName = false;
skippingAssemblyDetails = false;
builder.Append(current);
break;
case ',':
if (!writingAssemblyName)
{
writingAssemblyName = true;
builder.Append(current);
}
else
{
skippingAssemblyDetails = true;
}
break;
default:
if (!skippingAssemblyDetails)
builder.Append(current);
break;
}
}
return builder.ToString();
}
Note that that RemoveAssemblyDetails
method is ripped straight from the Json.Net source.
You will of course need to modify the WriteJson
method to output the rest of the fields, but hopefully that does the trick.
来源:https://stackoverflow.com/questions/14182961/making-custom-jsonconverter-respect-itemtypenamehandling-when-serializing-with-j