使用 Json.NET 禁用特定类型的类型注释

mca*_*ton 5 c# json.net

在远离$type注释的过程中(为了使数据语言独立),我在理解各种TypeNameHandling注释和类型契约的优先级方面遇到了一些问题。

在转换过程中,新类型和旧类型都将包含在相同的文件中。因此,该文件必须包含除新类型(ISettings实现)之外的所有类型的注释。

我试图用以下最小示例重现我的问题:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

.NET 小提琴链接

通过这个例子,我们得到以下信息:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

代替

{
   // 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"
        }
      }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

我试过在不同的地方应用TypeNameHandling/ItemTypeNameHandling注释但没有成功。我正在寻找的格式是否可以使用 Json.NET?

Dip*_*hah 1

更新:

我找到了一种在方法中动态设置属性的方法TypeNameHandlingJsonObjectContractCreateContract似乎已经解决了问题。在这里查看 .Net fiddler :

public class JsonConverterContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        JsonContract contract = base.CreateContract(objectType);

        if (contract is JsonObjectContract)
        {
            var objectContract = contract as JsonObjectContract;
            foreach (var property in objectContract.Properties)
            {
                var propertyType = property.PropertyType;
                if (IsTypeOfISettings(propertyType))
                {
                    // setting type name handling on property level
                    property.TypeNameHandling = TypeNameHandling.None;
                }
            }
        }

        // Here I'm hopping to disable type annotations for all `ISettings` instances.
        if (IsTypeOfISettings(objectType))
        {
            if (contract is JsonContainerContract objectContract)
            {
                objectContract.ItemTypeNameHandling = TypeNameHandling.None;
            }
        }

        return contract;
    }

    private bool IsTypeOfISettings(Type objectType)
    {
        return objectType == typeof(ISettings)
                || (objectType.IsClass && objectType.GetInterfaces().Any(i => i == typeof(ISettings)))
                || (objectType == typeof(IReadOnlyList<ISettings>))
                || (objectType == typeof(List<ISettings>))
                || (objectType == typeof(Dictionary<string, ISettings>));
    }
}
Run Code Online (Sandbox Code Playgroud)

原答案:

我相信是的,看看这个更新的.NET fiddle。我在这个小提琴中所做的唯一更改是替换ItemTypeNameHandlingTypeNameHandlingAllSettings属性,并且它有效!我通过查看 Newtonsoft.Json 的内部工作(特别是序列化器编写器)获得了灵感。

class AllSettings {
    public IDoNotTouchThis MustHaveType => new DoNotTouchThisClass();

    [JsonProperty(TypeNameHandling = TypeNameHandling.None)]
    public ISettings MustNotHaveType => new SettingsFoo();

    [JsonProperty(TypeNameHandling = TypeNameHandling.None)]
    public IReadOnlyList<ISettings> MustNotHaveTypeEither => new List<ISettings> {
        new SettingsFoo(),
        new SettingsBar(),
    };
}
Run Code Online (Sandbox Code Playgroud)

结果看起来像:

{
  "$type": "jsontest.AllSettings, tciypvk5.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
  "MustHaveType": {
    "$type": "jsontest.DoNotTouchThisClass, tciypvk5.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
    "MustHaveType": {
      "$type": "jsontest.HasType, tciypvk5.exe, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
    }
  },
  "MustNotHaveType": {
    "Foo": "NotImportant"
  },
  "MustNotHaveTypeEither": [
    {
      "Foo": "NotImportant"
    },
    {
      "Bar": "NotImportant",
      "SubSettings": {
        "Foo": "NotImportant"
      }
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)