2017-03-24 24 views
3

TypeNameHandlingを使用して、jsonの派生クラスのリストを直列化および逆シリアル化します。含まれるように、基本クラスを置くために属性がありTypeNameクラスの属性を持つハンドリング

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

: それは、プロパティと属性今JsonProperty

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

と完璧に動作し、私は、派生クラスのリストを返すWebAPIのを必要としますシリアル化の型?私は成功せず、試してみました:私はJsonSerializerSettings変更することができます知っている

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

しかし、私は

答えて

4

外-Oこれを行うには機能はありませんF-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、そしてアルバロムニョス&オレクサンドルMiroshのブラックハット紙https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdfを参照してください。

1

おそらく、基底クラスの属性理想的ではないとのソリューションをしたいが、あなたは簡単な回避策としてこれを試みることができる:

その後
[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; 
    } 
} 
+0

はい、チーム内でこの回避策を使用する場合、各開発者はリストの代わりにListAnimalsを作成してリストをシリアル化する必要があることを知っておく必要があります。だから私は、より柔軟なソリューションを望んだ – Troopers

関連する問題