如何在C#中将JSON对象序列化或反序列化到某个深度?

我只想要一个对象的第一个深度级别(我不想要任何孩子)。 我愿意使用任何可用的库。 大多数库只会在达到递归深度时抛出exception,而不是仅仅忽略。 如果这是不可能的,有没有办法忽略给定某种数据类型的某些成员的序列化?

编辑:假设我有一个像这样的对象:

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

我想删除任何子(甚至复杂类型)以返回这样的对象:

 class MyObject { String name = "Dan"; int age = 88; List myChildren = null; } 

这可以在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对将用于validation当前深度的所有属性应用特殊的ShouldSerialize谓词。

 public class CustomContractResolver : DefaultContractResolver { private readonly Func _includeProperty; public CustomContractResolver(Func 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 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; } } 

您可以使用reflection来检查对象,并根据需要创建一个更改每个属性值的副本。 巧合的是,我刚刚公开了一个新的图书馆,让这种事情变得非常简单。 你可以在这里得到它: https : //github.com/jamietre/IQObjectMapper

这是您将使用的代码示例

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

“Map”方法遍历对象的每个属性,并为每个属性调用Func (IDelegateInfo具有reflection信息,如属性名称,类型等)。 该函数返回每个属性的新值。 所以在这个例子中,我只测试每个属性的值以查看它是否是一个类,如果是,则返回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 kvp in dict) { if (kvp.Value!=null && kvp.Value.GetType().IsClass) { dict[kvp.Key]=null; } } // map back to an instance var newObject = ObjectMapper.ToNew(dict); 

在任何一种情况下, newInstance.myChildren (以及任何其他非值类型的属性)的值都将为null。 您可以轻松更改此映射中发生的情况的规则。

希望这可以帮助。 顺便说一句 – 从你的评论来看,听起来JSON并不是你真正的目标,只是你认为可以帮助你实现目标。 如果你想以json结束,只需序列化它的输出,例如

 string json = JavaScriptSerializer.Serialize(newObject); 

但如果那只是达到目的的手段,我就不会涉及json; 如果你想留在CLR对象中,那么就没有必要使用JSON作为中介。

首先,我想说所有的功劳应归功于Nathan Baulch。 这是他的答案与在设置中使用MaxDepth相结合的改编。 谢谢你的帮助Nathan!

 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 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 _includeProperty; public JsonNetContractResolver(Func 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 = <>, JsonRequestBehavior = JsonRequestBehavior.AllowGet, Settings = { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, MaxDepth = 1 } }; return result; 

如果要在ASP.NET Core项目中使用它,也许您无法实现自己的JsonTextWriter。 但您可以自定义DefaultContractResolver和IValueProvider

 using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Reflection; using System.Linq; namespace customserialization { ///  /// IValueProvider personalizado para manejar max depth level ///  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; } } ///  /// Maneja los niveles de serialización ///  public class MaxDepthHandler { int _maxDepth; int _currentDepthLevel; ///  /// Nivel actual ///  public int CurrentDepthLevel { get { return _currentDepthLevel; } } public MaxDepthHandler(int maxDepth) { this._currentDepthLevel = 1; this._maxDepth = maxDepth; } ///  /// Incrementa el nivel actual ///  public void IncrementLevel() { this._currentDepthLevel++; } ///  /// Decrementa el nivel actual ///  public void DecrementLevel() { this._currentDepthLevel--; } ///  /// Determina si se alcanzó el nivel actual ///  ///  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); }