2016-03-03 1 views
3

plutôt nouvelle à Json.net et essayé l'exemple simple suivant sérialisation et deserialing alors un objet à obtenir l'erreur ci-dessous:désérialisation un IEnumerable <T> avec [DataContract] appliquée ne fonctionne pas

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Newtonsoft.Json; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Runtime.Serialization; 
using System.Text; 
using System.Collections; 

namespace Timehunter.Base.ServicesTests 
{ 
    /// <summary> 
    /// Summary description for JsonError 
    /// </summary> 
    [TestClass] 
    public class JsonError 
    { 
    [TestMethod] 
     public void TestMethod1() 
     { 
      JsonSerializerSettings serializerSettings = new JsonSerializerSettings() 
      { 
       DateFormatHandling = DateFormatHandling.IsoDateFormat, 
       DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset 
      }; 

      Act.Activities acts = new Act.Activities(); 
      acts.Add(new Act.Activity() { Id = 1, Name = "test1" }); 
      acts.Add(new Act.Activity() { Id = 2, Name = "test2" }); 
      string json = Newtonsoft.Json.JsonConvert.SerializeObject(acts, serializerSettings); 

      Timehunter.Base.Act.Activities target = Newtonsoft.Json.JsonConvert.DeserializeObject<Timehunter.Base.Act.Activities>(json, serializerSettings); 
      Assert.AreEqual("test1", target.List[0].Name, "Name of first activity"); 
     } 
    } 
} 
namespace Timehunter.Base 
{ 
    [DataContract] 
    public class Activity 
    { 
     private int _id; 
     private string _name; 

     [DataMember] 
     public int Id 
     { 
      get { return this._id; } 
      set { this._id = value; } 
     } 
     [DataMember] 
     public string Name 
     { 
      get { return this._name; } 
      set { this._name = value; } 
     } 

     public Activity() 
     { 
      this._id = new int(); 
      this._name = string.Empty; 
     } 
    } 
    [DataContract] 
    public class Activities : IEnumerable<Activity> 
    { 
     private List<Activity> _list; 
     [DataMember] 
     public List<Activity> List 
     { 
      get { return this._list; } 
      set { this._list = value; } 
     } 
     public Activities() 
     { 
      this._list = new List<Activity>(); 
     } 

     public void Add(Activity item) 
     { this._list.Add(item); } 

     public bool Remove(Activity item) 
     { return this._list.Remove(item); } 

     public int Count() 
     { return this._list.Count; } 

     public IEnumerator<Activity> GetEnumerator() 
     { 
      return this._list.GetEnumerator(); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return GetEnumerator(); 
     } 
    } 
} 

Et puis-je obtenir la erreur suivante:

Test Name: TestMethod1 
Test FullName: Timehunter.Base.ServicesTests.JsonError.TestMethod1 
Test Source: C:\Users\hawi.HAWCONS\Documents\Visual Studio 2015\Projects\Timehunter.Data\Timehunter.Base.ServicesTests\JsonError.cs : line 67 
Test Outcome: Failed 
Test Duration: 0:00:00,2038359 

Result StackTrace: 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList(JsonReader reader, JsonArrayContract contract, Boolean& createdFromNonDefaultCreator) 
    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) 
    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) 
    at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) 
    at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) 
    at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) 
    at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) 
    at Timehunter.Base.ServicesTests.JsonError.TestMethod1() in C:\Users\hawi.HAWCONS\Documents\Visual Studio 2015\Projects\Timehunter.Data\Timehunter.Base.ServicesTests\JsonError.cs:line 79 
Result Message: 
Test method Timehunter.Base.ServicesTests.JsonError.TestMethod1 threw exception: 
Newtonsoft.Json.JsonSerializationException: Cannot create and populate list type Timehunter.Base.Act.Activities. Path '', line 1, position 1. 

Qu'est-ce que je fais mal?

Répondre

4

Mise à jour

présentées en tant que Issue #1598: DataContractAttribute does not cause JSon object serialization for IEnumerable et fixé à commettre e9e2d00. Il devrait être dans la prochaine version après 10.0.3 qui sera probablement la version Json.NET 11.

réponse originale

Je remarque que vous avez marqué votre classe Activities avec [DataContract] et [DataMember]:

[DataContract] 
public class Activities : IEnumerable<Activity> 
{ 
    private List<Activity> _list; 
    [DataMember] 
    public List<Activity> List 
    { 
     get { return this._list; } 
     set { this._list = value; } 
    } 
    // ... 
} 

Si vous appliquez [DataContact], DataContractJsonSerializer va sérialiser un IEnumerable<T> en tant qu'objet JSON avec des propriétés, plutôt qu'en tant que tableau JSON. Depuis Json.NET supports data contract attributes lorsqu'il est appliqué à des non-énumérables, vous pourriez penser qu'il les respectera également sur les énumérables et les collections.

Cependant, il semble que cela ne soit pas implémenté. Si je sérialiser votre classe avec DataContractJsonSerializer, je vois

{"List":[{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}]} 

Mais si je sérialiser avec Json.NET, je vois que le [DataContract] a été ignoré:

[{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}] 

Puis plus tard, il lance une exception lors de la désérialisation parce que il ne sait pas comment ajouter des membres à votre classe IEnumerable<Activity>. (Il aurait été possible d'ajouter des membres si votre classe a implémenté ICollection<Activity> ou had a constructor with an IEnumerable<Activity> argument.)

Alors, cela devrait-il fonctionner? La page de documentation Serialization Attributes déclare:

The DataContractAttribute can be used as substitute for JsonObjectAttribute. The DataContractAttribute will default member serialization to opt-in.

Ce qui implique que Json.NET doit travailler comme vous attendez. Vous pouvez le faire si vous voulez - au moins la documentation devrait être clarifiée.

Pour contourner ce problème, si vous voulez forcer Json.NET à sérialiser une collection comme un objet, vous devez utiliser à la place [JsonObject]:

[DataContract] 
[JsonObject(MemberSerialization = MemberSerialization.OptIn)] 
public class Activities : IEnumerable<Activity> 
{ 
    private List<Activity> _list; 

    [DataMember] 
    [JsonProperty] 
    public List<Activity> List 
    { 
     get { return this._list; } 
     set { this._list = value; } 
    } 

    // Remainder unchanged. 
} 

Si vous avez de nombreuses classes dénombrables avec [DataContract] appliqué, ou ne peut pas ajouter une dépendance sur Json.NET à vos modèles, vous pouvez créer un custom ContractResolver qui vérifie la présence de [DataContract] sur les classes dénombrables et les sérialise comme des objets:

public class DataContractForCollectionsResolver : 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." 
    static DataContractForCollectionsResolver instance; 

    static DataContractForCollectionsResolver() { instance = new DataContractForCollectionsResolver(); } 

    public static DataContractForCollectionsResolver Instance { get { return instance; } } 

    protected DataContractForCollectionsResolver() : base() { } 

    protected override JsonContract CreateContract(Type objectType) 
    { 
     var t = (Nullable.GetUnderlyingType(objectType) ?? objectType); 
     if (!t.IsPrimitive 
      && t != typeof(string) 
      && !t.IsArray 
      && typeof(IEnumerable).IsAssignableFrom(t) 
      && !t.GetCustomAttributes(typeof(JsonContainerAttribute),true).Any()) 
     { 
      if (t.GetCustomAttributes(typeof(DataContractAttribute),true).Any()) 
       return base.CreateObjectContract(objectType); 
     } 
     return base.CreateContract(objectType); 
    } 
} 

Ensuite, utilisez les paramètres suivants:

var serializerSettings = new JsonSerializerSettings() 
{ 
    DateFormatHandling = DateFormatHandling.IsoDateFormat, 
    DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset, 
    ContractResolver = DataContractForCollectionsResolver.Instance 
}; 
+0

Thanx mais je devais changer la méthode CreateContract un peu afin de le faire fonctionner pour moi: 'code' ... &&! t.GetCustomAttributes (typeof (JsonContainerAttribute), true) .Any()) { if (t.GetCustomAttributes (typeof (DataContractAttribute), true) .Any()) return base.CreateObjectContract (objetType); ... – hawi

+0

@hawi - réponse mise à jour comme vous le proposez. On dirait que Json.NET [traite 'DataContract' comme hérité même si les sérialiseurs de contrat de données ne le font pas.] (Http://json.codeplex.com/discussions/357850). – dbc