2012-05-04 24 views
11

我只想要一個對象的第一個深度級別(我不想要任何孩子)。我願意使用任何可用的圖書館。當達到遞歸深度時,大多數庫只會拋出一個異常,而不僅僅是忽略。如果這是不可能的,有沒有辦法忽略給定某個數據類型的某些成員的序列化?如何將JSON對象序列化或反序列化爲C#中的特定深度?

編輯: 比方說,我有這樣一個對象,以便:

class MyObject 
{ 
    String name = "Dan"; 
    int age = 88; 
    List<Children> myChildren = ...(lots of children with lots of grandchildren); 
} 

我想刪除任何子女(複雜類型偶數)返回這樣一個對象:

class MyObject 
{ 
    String name = "Dan"; 
    int age = 88; 
    List<Children> myChildren = null; 
} 
+0

你能告訴問題JSON的一些例子嗎? – igofed

+1

你可以創建一個沒有任何子對象的新對象並將其序列化嗎? –

+0

從邏輯上講,這是有道理的,但我想剝離其子對象的任何類型的對象。我認爲json序列化將是實現這一目標的最佳方式,但我絕對樂於接受建議。 –

回答

24

這是可能在Json.NET使用JsonWriter和串行器的ContractResolver之間的一些協調。

自定義JsonWriter當對象啓動時遞增計數器,然後在結束時再次遞減計數器。

public class CustomJsonTextWriter : JsonTextWriter 
{ 
    public CustomJsonTextWriter(TextWriter textWriter) : base(textWriter) {} 

    public int CurrentDepth { get; private set; } 

    public override void WriteStartObject() 
    { 
     CurrentDepth++; 
     base.WriteStartObject(); 
    } 

    public override void WriteEndObject() 
    { 
     CurrentDepth--; 
     base.WriteEndObject(); 
    } 
} 

定製ContractResolver適用於將被用於驗證當前深度的所有屬性的特殊ShouldSerialize謂語。

public class CustomContractResolver : DefaultContractResolver 
{ 
    private readonly Func<bool> _includeProperty; 

    public CustomContractResolver(Func<bool> includeProperty) 
    { 
     _includeProperty = includeProperty; 
    } 

    protected override JsonProperty CreateProperty(
     MemberInfo member, MemberSerialization memberSerialization) 
    { 
     var property = base.CreateProperty(member, memberSerialization); 
     var shouldSerialize = property.ShouldSerialize; 
     property.ShouldSerialize = obj => _includeProperty() && 
              (shouldSerialize == null || 
              shouldSerialize(obj)); 
     return property; 
    } 
} 

以下方法顯示了這兩個自定義類如何一起工作。

public static string SerializeObject(object obj, int maxDepth) 
{ 
    using (var strWriter = new StringWriter()) 
    { 
     using (var jsonWriter = new CustomJsonTextWriter(strWriter)) 
     { 
      Func<bool> include =() => jsonWriter.CurrentDepth <= maxDepth; 
      var resolver = new CustomContractResolver(include); 
      var serializer = new JsonSerializer {ContractResolver = resolver}; 
      serializer.Serialize(jsonWriter, obj); 
     } 
     return strWriter.ToString(); 
    } 
} 

以下測試代碼演示了將最大深度分別限制爲1和2級別。

var obj = new Node { 
    Name = "one", 
    Child = new Node { 
     Name = "two", 
     Child = new Node { 
      Name = "three" 
     } 
    } 
}; 
var txt1 = SerializeObject(obj, 1); 
var txt2 = SerializeObject(obj, 2); 

public class Node 
{ 
    public string Name { get; set; } 
    public Node Child { get; set; } 
} 
+0

我無法讓它在Json.Net庫的實際版本中工作。看來CustomContractResolvers方法永遠不會被調用。 – Kjellski

+0

對不起,我錯過了它明確說的部分:CreatePROPERTY ...我的壞。對於普通會員需要更多的洞察力嗎?任何解決方案 – Kjellski

+13

非常傷心,[JsonSerializerSettings.MaxDepth'屬性](http://james.newtonking.com/projects/json/JPG/help/html/P_Newtonsoft_Json_Json_JsonSerializerSettings_MaxDepth.htm)沒有照顧這個 – drzaus

1

您可以使用反射來檢查對象,並根據需要製作一個可以更改每個屬性值的副本。巧合的是,我剛剛公佈了一個新庫,使這種事情變得非常簡單。你可以在這裏得到它:https://github.com/jamietre/IQObjectMapper

下面的代碼示例,你會使用

var newInstance = ObjectMapper.Map(obj,(value,del) => { 
    return value !=null && value.GetType().IsClass ? 
     null : 
     value; 
    }); 

的通過對象的每個屬性「地圖」方法循環,​​並呼籲每個Func<object,IDelegateInfo>(具有反射IDelegateInfo信息,如屬性名稱,類型等)。該函數返回每個屬性的新值。所以在這個例子中,我只是測試每個屬性的值以查看它是否是一個類,如果是,則返回null;如果不是,則返回原始值。

另一種更具表現力的方式來做到這一點:

var obj = new MyObject(); 

// map the object to a new dictionary   

var dict = ObjectMapper.ToDictionary(obj); 

// iterate through each item in the dictionary, a key/value pair 
// representing each property 

foreach (KeyValuePair<string,object> kvp in dict) { 
    if (kvp.Value!=null && kvp.Value.GetType().IsClass) { 
     dict[kvp.Key]=null; 
    } 
} 

// map back to an instance 

var newObject = ObjectMapper.ToNew<MyObject>(dict); 

在任一情況下,newInstance.myChildren值(以及是非值類型的任何其他屬性)將是無效的。您可以輕鬆更改此映射中發生的規則。

希望這會有所幫助。順便說一句 - 從您的評論聽起來像JSON不是你的目標,而只是你認爲會幫助你實現它的東西。如果你想結束json,只需要序列化這個輸出,例如

string json = JavaScriptSerializer.Serialize(newObject); 

但我不會涉及json如果這只是一種手段,以結束;如果你想留在CLR對象中,那麼真的不需要使用JSON作爲中介。

0

首先,我想說所有功勞都應該去Nathan Baulch。這是他的回答與在設置中使用MaxDepth相結合的改編。謝謝你的幫助彌敦道!

using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Reflection; 
using System.Web; 
using System.Web.Mvc; 

namespace Helpers 
{ 
    public class JsonNetResult : JsonResult 
    { 
     public JsonNetResult() 
     { 
      Settings = new JsonSerializerSettings 
      { 
       ReferenceLoopHandling = ReferenceLoopHandling.Error 
      }; 
     } 

     public JsonSerializerSettings Settings { get; private set; } 

     public override void ExecuteResult(ControllerContext context) 
     { 
      if (context == null) 
       throw new ArgumentNullException("context"); 
      if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) 
       throw new InvalidOperationException("JSON GET is not allowed"); 

      HttpResponseBase response = context.HttpContext.Response; 
      response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType; 

      if (this.ContentEncoding != null) 
       response.ContentEncoding = this.ContentEncoding; 
      if (this.Data == null) 
       return; 

      var scriptSerializer = JsonSerializer.Create(this.Settings); 

      using (var sw = new StringWriter()) 
      { 
       if (Settings.MaxDepth != null) 
       { 
        using (var jsonWriter = new JsonNetTextWriter(sw)) 
        { 
         Func<bool> include =() => jsonWriter.CurrentDepth <= Settings.MaxDepth; 
         var resolver = new JsonNetContractResolver(include); 
         this.Settings.ContractResolver = resolver; 
         var serializer = JsonSerializer.Create(this.Settings); 
         serializer.Serialize(jsonWriter, Data); 
        } 
        response.Write(sw.ToString()); 
       } 
       else 
       { 
        scriptSerializer.Serialize(sw, this.Data); 
        response.Write(sw.ToString()); 
       } 
      } 
     } 
    } 

    public class JsonNetTextWriter : JsonTextWriter 
    { 
     public JsonNetTextWriter(TextWriter textWriter) : base(textWriter) { } 

     public int CurrentDepth { get; private set; } 

     public override void WriteStartObject() 
     { 
      CurrentDepth++; 
      base.WriteStartObject(); 
     } 

     public override void WriteEndObject() 
     { 
      CurrentDepth--; 
      base.WriteEndObject(); 
     } 
    } 

    public class JsonNetContractResolver : DefaultContractResolver 
    { 
     private readonly Func<bool> _includeProperty; 

     public JsonNetContractResolver(Func<bool> includeProperty) 
     { 
      _includeProperty = includeProperty; 
     } 

     protected override JsonProperty CreateProperty(
      MemberInfo member, MemberSerialization memberSerialization) 
     { 
      var property = base.CreateProperty(member, memberSerialization); 
      var shouldSerialize = property.ShouldSerialize; 
      property.ShouldSerialize = obj => _includeProperty() && 
               (shouldSerialize == null || 
               shouldSerialize(obj)); 
      return property; 
     } 
    } 
} 

用途:

// instantiating JsonNetResult to handle circular reference issue. 
var result = new JsonNetResult 
{ 
    Data = <<The results to be returned>>, 
    JsonRequestBehavior = JsonRequestBehavior.AllowGet, 
    Settings = 
     { 
      ReferenceLoopHandling = ReferenceLoopHandling.Ignore, 
      MaxDepth = 1 
     } 
}; 

return result; 
0

如果你想在ASP.NET核心項目使用這個,也許你無法實現自己的JsonTextWriter。但是你可以自定義的DefaultContractResolver和IValueProvider

using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 
using System; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Linq; 

namespace customserialization 
{ 
    /// <summary> 
    /// IValueProvider personalizado para manejar max depth level 
    /// </summary> 
    public class CustomDynamicValueProvider : DynamicValueProvider, IValueProvider 
    { 
     MemberInfo _memberInfo; 
     MaxDepthHandler _levelHandler; 

     public CustomDynamicValueProvider(MemberInfo memberInfo, MaxDepthHandler levelHandler) : base(memberInfo) 
     { 
      _memberInfo = memberInfo; 
      _levelHandler = levelHandler; 
     } 

     public new object GetValue(object target) 
     { 
      //Si el valor a serializar es un objeto se incrementa el nivel de profundidad. En el caso de las listas el nivel se incrementa en el evento OnSerializing 
      if (((PropertyInfo)_memberInfo).PropertyType.IsClass) this._levelHandler.IncrementLevel(); 

      var rv = base.GetValue(target); 

      //Al finalizar la obtención del valor se decrementa. En el caso de las listas el nivel se decrementa en el evento OnSerialized 
      if (((PropertyInfo)_memberInfo).PropertyType.IsClass) this._levelHandler.DecrementLevel(); 

      return rv; 
     } 
    } 

    /// <summary> 
    /// Maneja los niveles de serialización 
    /// </summary> 
    public class MaxDepthHandler 
    { 
     int _maxDepth; 
     int _currentDepthLevel; 

     /// <summary> 
     /// Nivel actual 
     /// </summary> 
     public int CurrentDepthLevel { get { return _currentDepthLevel; } } 

     public MaxDepthHandler(int maxDepth) 
     { 
      this._currentDepthLevel = 1; 
      this._maxDepth = maxDepth; 
     } 

     /// <summary> 
     /// Incrementa el nivel actual 
     /// </summary> 
     public void IncrementLevel() 
     { 
      this._currentDepthLevel++; 
     } 

     /// <summary> 
     /// Decrementa el nivel actual 
     /// </summary> 
     public void DecrementLevel() 
     { 
      this._currentDepthLevel--; 
     } 

     /// <summary> 
     /// Determina si se alcanzó el nivel actual 
     /// </summary> 
     /// <returns></returns> 
     public bool IsMaxDepthLevel() 
     { 
      return !(this._currentDepthLevel < this._maxDepth); 
     } 
    } 

    public class ShouldSerializeContractResolver : DefaultContractResolver 
    { 

     MaxDepthHandler _levelHandler; 

     public ShouldSerializeContractResolver(int maxDepth) 
     { 
      this._levelHandler = new MaxDepthHandler(maxDepth); 
     } 


     void OnSerializing(object o, System.Runtime.Serialization.StreamingContext context) 
     { 
      //Antes de serializar una lista se incrementa el nivel. En el caso de los objetos el nivel se incrementa en el método GetValue del IValueProvider 
      if (o.GetType().IsGenericList()) 
       _levelHandler.IncrementLevel(); 
     } 

     void OnSerialized(object o, System.Runtime.Serialization.StreamingContext context) 
     { 
      //Despues de serializar una lista se decrementa el nivel. En el caso de los objetos el nivel se decrementa en el método GetValue del IValueProvider 
      if (o.GetType().IsGenericList()) 
       _levelHandler.DecrementLevel(); 
     } 

     protected override JsonContract CreateContract(Type objectType) 
     { 
      var contract = base.CreateContract(objectType); 
      contract.OnSerializingCallbacks.Add(new SerializationCallback(OnSerializing)); 
      contract.OnSerializedCallbacks.Add(new SerializationCallback(OnSerialized)); 

      return contract; 
     } 


     protected override IValueProvider CreateMemberValueProvider(MemberInfo member) 
     { 
      var rv = base.CreateMemberValueProvider(member); 

      if (rv is DynamicValueProvider) //DynamicValueProvider es el valueProvider usado en general 
      { 
       //Utilizo mi propio ValueProvider, que utilizar el levelHandler 
       rv = new CustomDynamicValueProvider(member, this._levelHandler); 
      } 

      return rv; 
     } 

     protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 
     { 
      JsonProperty property = base.CreateProperty(member, memberSerialization); 

      var isObjectOrList = ((PropertyInfo)member).PropertyType.IsGenericList() || ((PropertyInfo)member).PropertyType.IsClass; 



      property.ShouldSerialize = 
        instance => 
        { 
         var shouldSerialize = true; 
         //Si se alcanzo el nivel maximo y la propiedad (member) actual a serializar es un objeto o lista no se serializa (shouldSerialize = false) 
         if (_levelHandler.IsMaxDepthLevel() && isObjectOrList) 
          shouldSerialize = false;       

         return shouldSerialize; 
        }; 

      return property; 
     } 



    } 

    public static class Util 
    { 
     public static bool IsGenericList(this Type type) 
     { 
      foreach (Type @interface in type.GetInterfaces()) 
      { 
       if (@interface.IsGenericType) 
       { 
        if (@interface.GetGenericTypeDefinition() == typeof(ICollection<>)) 
        { 
         // if needed, you can also return the type used as generic argument 
         return true; 
        } 
       } 
      } 
      return false; 
     } 
    } 
} 

,並使用這個在您的控制器

 [HttpGet] 
     public IActionResult TestJSON() 
     { 
      var obj = new Thing 
      { 
       id = 1, 
       reference = new Thing 
       { 
        id = 2, 
        reference = new Thing 
        { 
         id = 3, 
         reference = new Thing 
         { 
          id = 4 
         } 
        } 
       } 
      }; 
      var settings = new JsonSerializerSettings() 
      { 
       ContractResolver = new ShouldSerializeContractResolver(2), 
      }; 

      return new JsonResult(obj, settings); 

     }