There is no functionality to do this out-of-the box in Json.NET, but you could do it with a custom contract resolver:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public class AddJsonTypenameAttribute : System.Attribute
{
}
public class AddJsonTypenameContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static AddJsonTypenameContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static AddJsonTypenameContractResolver() { instance = new AddJsonTypenameContractResolver(); }
public static AddJsonTypenameContractResolver Instance { get { return instance; } }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
return base.CreateProperty(member, memberSerialization)
.ApplyAddTypenameAttribute();
}
protected override JsonArrayContract CreateArrayContract(Type objectType)
{
return base.CreateArrayContract(objectType)
.ApplyAddTypenameAttribute();
}
}
public static class ContractResolverExtensions
{
public static JsonProperty ApplyAddTypenameAttribute(this JsonProperty jsonProperty)
{
if (jsonProperty.TypeNameHandling == null)
{
if (jsonProperty.PropertyType.GetCustomAttribute<AddJsonTypenameAttribute>(false) != null)
{
jsonProperty.TypeNameHandling = TypeNameHandling.All;
}
}
return jsonProperty;
}
public static JsonArrayContract ApplyAddTypenameAttribute(this JsonArrayContract contract)
{
if (contract.ItemTypeNameHandling == null)
{
if (contract.CollectionItemType.GetCustomAttribute<AddJsonTypenameAttribute>(false) != null)
{
contract.ItemTypeNameHandling = TypeNameHandling.All;
}
}
return contract;
}
}
Then apply it to your interfaces or base types as follows:
[AddJsonTypename]
public interface IAnimal
{
bool CanFly { get; }
}
[AddJsonTypename]
public abstract class Animal : IAnimal
{
public bool CanFly { get; set; }
}
Note that the attribute is marked with Inherited = false
. This means a List<Animal>
will have type information inserted automatically but a List<FlyingAnimal>
will not. Also note that this will not force type information to be emitted for a root object. If you need this, see perhaps here.
Finally, to use the contract resolver with Web API, see for instance Web API 2: how to return JSON with camelCased property names, on objects and their sub-objects. And do take note of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json, How to configure Json.NET to create a vulnerable web API, and Alvaro Mu?oz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…