我有一个情况,我有一个非常大的C#对象,但是,我只需要返回一些属性(可以在嵌套对象上),允许客户端JavaScript修改这些属性,然后发送生成的对象返回服务器以执行就地部分反序列化.
我们的想法是重用一些非常大的现有业务对象,但要智能化,只将序列化并仅将这些属性发送回客户端应用程序进行修改(以便将传输的数据量保持在最低限度).
我基本上有一个XML文件,我使用“路径语法”预先定义所有绑定,这只表示我需要序列化的那些属性.所以,我可以使用“WorkOrder.UserField1”或“WorkOrder.Client.Name”之类的东西.
我尝试使用自定义合约解析程序来确定是否应该序列化属性;但是,我似乎没有关于“路径”(换句话说,链中对象模型中的其他属性)的信息,以确定属性是否应该序列化.
我也尝试过使用自定义的JsonTextWriter,但是我似乎无法覆盖跟踪路径所需的方法,即使有一个Path属性可用.为了能够查看被序列化的属性的路径层次结构并确定是否应该通过在表中查找路径并做出决定来序列化,是否有一些我可能会忽略的简单内容?
这里的基本困难是Json.NET是一个基于合同的序列化程序,它为每个要序列化的类型创建一个合同,然后根据合同(de)序列化.如果类型出现在对象层次结构中的多个位置,则应用相同的合同.但是,您希望根据层次结构中的位置选择性地包含给定类型的属性,这与基本的“一类合同”设计冲突.
解决此问题的一种快速方法是序列化为JObject
,然后使用JToken.SelectTokens()
仅选择要返回的JSON数据,删除其他所有内容.由于SelectTokens完全支持JsonPATH
query syntax,因此您可以选择性地使用数组和属性通配符或其他过滤器,例如:
"$.FirstLevel[*].Bar"
包括根对象的名为“FirstLevel”的属性的所有数组成员中名为“Bar”的所有属性.
这应该会根据需要减少网络使用,但不会在服务器上节省任何处理时间.
public static class JsonExtensions { public static void RemoveAllExcept(this JObject obj,IEnumerable<string> paths) { if (obj == null || paths == null) throw new NullReferenceException(); var keepers = new HashSet<JToken>(paths.SelectMany(path => obj.SelectTokens(path))); var keepersAndParents = new HashSet<JToken>(keepers.SelectMany(t => t.AncestorsAndSelf())); // Keep any token that is a keeper,or a child of a keeper,or a parent of a keeper // I.e. if you have a path ""$.A.B" and it turns out that B is an object,then everything // under B should be kept. foreach (var token in obj.DescendantsAndSelfReversed().Where(t => !keepersAndParents.Contains(t) && !t.AncestorsAndSelf().Any(p => keepers.Contains(p)))) token.RemoveFromLowestPossibleParent(); } public static void RemoveFromLowestPossibleParent(this JToken node) { if (node == null) throw new ArgumentNullException(); var contained = node.AncestorsAndSelf().Where(t => t.Parent is JArray || t.Parent is JObject).FirstOrDefault(); if (contained != null) contained.Remove(); } public static IEnumerable<JToken> DescendantsAndSelfReversed(this JToken node) { if (node == null) throw new ArgumentNullException(); return RecursiveEnumerableExtensions.Traverse(node,t => ListReversed(t as JContainer)); } // Iterate backwards through a list without throwing an exception if the list is modified. static IEnumerable<T> ListReversed<T>(this IList<T> list) { if (list == null) yield break; for (int i = list.Count - 1; i >= 0; i--) yield return list[i]; } } public static partial class RecursiveEnumerableExtensions { // Rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert // to "Efficient graph traversal with LINQ - eliminating recursion" http://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion // to ensure items are returned in the order they are encountered. public static IEnumerable<T> Traverse<T>( T root,Func<T,IEnumerable<T>> children) { yield return root; var stack = new Stack<IEnumerator<T>>(); try { stack.Push((children(root) ?? Enumerable.Empty<T>()).GetEnumerator()); while (stack.Count != 0) { var enumerator = stack.Peek(); if (!enumerator.MoveNext()) { stack.Pop(); enumerator.Dispose(); } else { yield return enumerator.Current; stack.Push((children(enumerator.Current) ?? Enumerable.Empty<T>()).GetEnumerator()); } } } finally { foreach (var enumerator in stack) enumerator.Dispose(); } } }
然后使用它们像:
public class TestClass { public static string SerializeAndSelectTokens<T>(T root,string[] paths) { var obj = JObject.FromObject(root); obj.RemoveAllExcept(paths); Debug.WriteLine(obj.ToString(Formatting.Indented)); var json = obj.ToString(Formatting.None); return json; } public static void Test() { var root = new RootObject { FirstLevel1 = new FirstLevel { SecondLevel1 = new List<SecondLevel> { new SecondLevel { A = "a11",B = "b11",Third1 = new ThirdLevel { Foo = "Foos11",Bar = "Bars11" },Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList11",Bar = "BarList11" } } } },SecondLevel2 = new List<SecondLevel> { new SecondLevel { A = "a12",B = "b12",Third1 = new ThirdLevel { Foo = "Foos12",Bar = "Bars12" },Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList12",Bar = "BarList12" } } } },},FirstLevel2 = new FirstLevel { SecondLevel1 = new List<SecondLevel> { new SecondLevel { A = "a21",B = "b21",Third1 = new ThirdLevel { Foo = "Foos21",Bar = "Bars21" },Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList21",Bar = "BarList21" } } } },SecondLevel2 = new List<SecondLevel> { new SecondLevel { A = "a22",B = "b22",Third1 = new ThirdLevel { Foo = "Foos22",Bar = "Bars22" },Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList22",Bar = "BarList22" } } } },} }; Debug.Assert(JObject.FromObject(root).DescendantsAndSelf().OfType<JValue>().Count() == 24); // No assert var paths1 = new string[] { "$.FirstLevel2.SecondLevel1[*].A","$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",}; var json1 = SerializeAndSelectTokens(root,paths1); Debug.Assert(JObject.Parse(json1).DescendantsAndSelf().OfType<JValue>().Count() == 2); // No assert var paths3 = new string[] { "$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",}; var json3 = SerializeAndSelectTokens(root,paths3); Debug.Assert(JObject.Parse(json3).DescendantsAndSelf().OfType<JValue>().Count() == 1); // No assert var paths4 = new string[] { "$.*.SecondLevel2[*].Third2[*].Bar",}; var json4 = SerializeAndSelectTokens(root,paths4); Debug.Assert(JObject.Parse(json4).DescendantsAndSelf().OfType<JValue>().Count() == 2); // No assert } } public class ThirdLevel { public string Foo { get; set; } public string Bar { get; set; } } public class SecondLevel { public ThirdLevel Third1 { get; set; } public List<ThirdLevel> Third2 { get; set; } public string A { get; set; } public string B { get; set; } } public class FirstLevel { public List<SecondLevel> SecondLevel1 { get; set; } public List<SecondLevel> SecondLevel2 { get; set; } } public class RootObject { public FirstLevel FirstLevel1 { get; set; } public FirstLevel FirstLevel2 { get; set; } }