TypeNameHandling包含类的属性

时间:2017-03-24 09:48:30

标签: c# json.net

我使用TypeNameHandling在json中序列化和反序列化派生类的列表。 它与属性和属性public abstract class Animal { public bool CanFly { get; set;} } public class FlyingAnimal : Animal { public FlyingAnimal() { this.CanFly = true; } } public class SwimmingAnimal : Animal { public SwimmingAnimal() { this.CanFly = false; } } public class World { public World() { this.Animals = new List<Animal>(); this.Animals.Add(new FlyingAnimal()); this.Animals.Add(new SwimmingAnimal()); } [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)] public List<Animal> Animals { get; set; } }

完美配合
[RoutePrefix("Animals")]
public class AnimalsController : ApiController
{
    public List<Animal> Get()
    {
        List<Animal> animals = new List<Animal>(); 
        animals.Add(new FlyingAnimal());
        animals.Add(new SwimmingAnimal());
        return animals;
    }
}

现在,我需要一个返回派生类列表的WebAPI:

[JsonObject(ItemTypeNameHandling = TypeNameHandling.Auto)]
public abstract class Animal

是否有一个属性要放在基类上以包含序列化中的类型?我尝试没有成功:

JsonSerializerSettings

我知道我可以更改{{1}},但我想要一个基类属性的解决方案

2 个答案:

答案 0 :(得分:8)

在Json.NET中没有开箱即用的功能,但你可以用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;
    }
}

然后将其应用于您的接口或基本类型,如下所示:

[AddJsonTypename]
public interface IAnimal
{
    bool CanFly { get; }
}

[AddJsonTypename]
public abstract class Animal : IAnimal
{
    public bool CanFly { get; set; }
}

请注意,该属性标有Inherited = false。这意味着List<Animal>会自动插入类型信息,但List<FlyingAnimal>则不会。另请注意,这不会强制为根对象发出类型信息。如果您需要,请参阅here

最后,要将合约解析程序与Web API一起使用,请参阅例如Web API 2: how to return JSON with camelCased property names, on objects and their sub-objects。请注意Newtonsoft docs

中的这一注意事项
  

当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型。

有关为何需要这样做的讨论,请参阅TypeNameHandling caution in Newtonsoft JsonHow to configure Json.NET to create a vulnerable web API和AlvaroMuñoz&amp; Oleksandr Mirosh的黑帽纸https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

答案 1 :(得分:2)

也许并不理想,但您可以尝试将其作为一种简单的解决方法:

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

然后:

[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;
    }
}