TypeNameHandling with attribute on a class

前端 未结 2 2035
情深已故
情深已故 2021-01-19 06:40

I use TypeNameHandling to serialize and deserialize a list of derived class in json. It work perfectly with a property and the attribute JsonProperty

         


        
相关标签:
2条回答
  • 2021-01-19 06:56

    Perhaps not ideal, but you could try this as a simple workaround:

    [JsonArray(ItemTypeNameHandling = TypeNameHandling.Auto)]
    public class AnimalList : List<Animal>
    { }
    

    Then:

    [RoutePrefix("Animals")]
    public class AnimalsController : ApiController
    {
        public List<Animal> Get()
        {
            List<Animal> animals = new AnimalList(); 
            animals.Add(new FlyingAnimal());
            animals.Add(new SwimmingAnimal());
            return animals;
        }
    }
    
    0 讨论(0)
  • 2021-01-19 07:07

    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.

    0 讨论(0)
提交回复
热议问题