In the process of moving away from $type annotations (in order to make the data language-independent), I'm having some issues understanding the priority of the various TypeNameHandling annotations and type contract.
During the transiton, both the new types and old types will be contained in the same files. Therefore the file must contain the type annotations on all but the new types (ISettings implementations).
I've tried to reproduce my problem with the following minimum example:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using Newtonsoft.Json.Serialization;
namespace jsontest
{
// I can't touch this class, even to add annotations
// (in practice there are too many classes to update)
interface IDoNotTouchThis {}
class DoNotTouchThisClass : IDoNotTouchThis {
public IMustHaveType MustHaveType => new HasType();
}
// This is the old data. It must have type annotations to be deserialized.
interface IMustHaveType {}
class HasType : IMustHaveType {}
// This is the new data. It must not have type annotations.
// There is a `JsonConverter` to figure out the types according to the fields.
interface ISettings {}
class SettingsFoo: ISettings {
public String Foo => "NotImportant";
}
class SettingsBar: ISettings {
public String Bar => "NotImportant";
public ISettings SubSettings => new SettingsFoo();
}
// This is the top-level class of the data.
class AllSettings {
public IDoNotTouchThis MustHaveType => new DoNotTouchThisClass();
public ISettings MustNotHaveType => new SettingsFoo();
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.None)] // This helps, but isn't enough
public IReadOnlyList<ISettings> MustNotHaveTypeEither => new List<ISettings> {
new SettingsFoo(),
new SettingsBar(),
};
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Program.Serialize(new AllSettings()));
}
private static JsonSerializerSettings SerializeSettings { get; }
= new JsonSerializerSettings()
{
// For backward compatibility:
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full,
ContractResolver = new JsonConverterContractResolver(),
};
private static string Serialize<T>(T o)
{
return JsonConvert.SerializeObject(
o,
Formatting.Indented,
Program.SerializeSettings);
}
}
public class JsonConverterContractResolver : DefaultContractResolver
{
/// <inheritdoc />
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
// Here I'm hopping to disable type annotations for all `ISettings` instances.
if (objectType == typeof(ISettings)
|| (objectType.IsClass && objectType.GetInterfaces().Any(i => i == typeof(ISettings)))
|| (objectType == typeof(IReadOnlyList<ISettings>))
|| (objectType == typeof(List<ISettings>)))
{
if (contract is JsonContainerContract objectContract)
{
objectContract.ItemTypeNameHandling = TypeNameHandling.None;
}
}
return contract;
}
}
}
With this example, we're getting the following:
{
// Not necessary, but doesn't hurt:
"$type": "jsontest.AllSettings, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"MustHaveType": {
"$type": "jsontest.DoNotTouchThisClass, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"MustHaveType": {
"$type": "jsontest.HasType, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
}
},
"MustNotHaveType": {
"$type": "jsontest.SettingsFoo, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"Foo": "NotImportant"
},
"MustNotHaveTypeEither": {
"$type": "System.Collections.Generic.List`1[[jsontest.ISettings, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e",
"$values": [
{
"Foo": "NotImportant"
},
{
"Bar": "NotImportant",
"SubSettings": {
"Foo": "NotImportant"
}
}
]
}
}
instead of
{
// Not necessary, but doesn't hurt:
"$type": "jsontest.AllSettings, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"MustHaveType": {
"$type": "jsontest.DoNotTouchThisClass, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"MustHaveType": {
"$type": "jsontest.HasType, pzyc3cmg.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
}
},
"MustNotHaveType": {
"Foo": "NotImportant"
},
"MustNotHaveTypeEither": [
{
"Foo": "NotImportant"
},
{
"Bar": "NotImportant",
"SubSettings": {
"Foo": "NotImportant"
}
}
]
}
I've tried applying TypeNameHandling/ItemTypeNameHandling annotations at different places without success. Is the format I'm looking for possible with Json.NET?