如何使用Newtonsoft JSON.NET执行提供“路径”的部分对象序列化

如何使用Newtonsoft JSON.NET执行提供“路径”的部分对象序列化,第1张

如何使用Newtonsoft JSON.NET执行提供“路径”的部分对象序列化

这里的基本困难是Json.NET是基于契约的序列化器,它为要序列化的每种 类型
创建契约,然后根据契约对序列进行(反)序列化。如果类型出现在对象层次结构的多个位置,则适用相同的合同。但是您要根据给定类型在层次结构中的位置有选择地包括属性,这与基本的“一种类型一个合同”设计冲突。

解决此问题的一种快速方法是将序列化为

JObject
,然后
JToken.SelectTokens()
仅选择要返回的JSON数据,然后删除其他所有内容。由于
SelectTokens
完全支持JSONPath查询语法,因此您可以有选择地包括使用数组和属性通配符或其他过滤器,例如:

"$.FirstLevel[*].Bar"

包括

"Bar"
在以
"FirstLevel"
根对象命名的属性的所有数组成员中命名的所有属性。

这样可以减少所需的网络使用量,但不会节省服务器上的任何处理时间。

可以通过以下扩展方法完成删除:

public static partial class JsonExtensions{    public static TJToken RemoveAllExcept<TJToken>(this TJToken obj, IEnumerable<string> paths) where TJToken : JToken    {        if (obj == null || paths == null) throw new NullReferenceException();        var keepers = new HashSet<JToken>(paths.SelectMany(path => obj.SelectTokens(path)), ObjectReferenceEqualityComparer<JToken>.Default);        var keepersAndParents = new HashSet<JToken>(keepers.SelectMany(t => t.AncestorsAndSelf()), ObjectReferenceEqualityComparer<JToken>.Default);        // 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();        // Return the object itself for fluent style programming.        return obj;    }    public static string SerializeAndSelectTokens<T>(T root, string[] paths, Formatting formatting = Formatting.None, JsonSerializerSettings settings = null)    {        var obj = JObject.FromObject(root, JsonSerializer.CreateDefault(settings));        obj.RemoveAllExcept(paths);        var json = obj.ToString(formatting);        return json;    }    public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken    {        if (node == null) return null;        JToken toRemove;        var property = node.Parent as JProperty;        if (property != null)        { // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should toRemove = property; property.Value = null;        }        else        { toRemove = node;        }        if (toRemove.Parent != null) toRemove.Remove();        return node;    }    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();        }    }}/// <summary>/// A generic object comparerer that would only use object's reference, /// ignoring any <see cref="IEquatable{T}"/> or <see cref="object.Equals(object)"/>  overrides./// </summary>public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class{    // Adapted from this answer https://stackoverflow.com/a/1890230    // to https://stackoverflow.com/questions/1890058/iequalitycomparert-that-uses-referenceequals    // By https://stackoverflow.com/users/177275/yurik    private static readonly IEqualityComparer<T> _defaultComparer;    static ObjectReferenceEqualityComparer() { _defaultComparer = new ObjectReferenceEqualityComparer<T>(); }    public static IEqualityComparer<T> Default { get { return _defaultComparer; } }    #region IEqualityComparer<T> Members    public bool Equals(T x, T y)    {        return ReferenceEquals(x, y);    }    public int GetHashCode(T obj)    {        return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);    }    #endregion}

然后像这样使用它们:

public class TestClass{    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" } } } }, }        };        Assert.IsTrue(JObject.FromObject(root).DescendantsAndSelf().OfType<JValue>().Count() == 24); // No assert        var paths1 = new string[]         { "$.FirstLevel2.SecondLevel1[*].A", "$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",        };        Test(root, paths1, 2);        var paths3 = new string[]         { "$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",        };        Test(root, paths3, 1);        var paths4 = new string[]         { "$.*.SecondLevel2[*].Third2[*].Bar",        };        Test(root, paths4, 2);    }    static void Test<T>(T root, string [] paths, int expectedCount)    {        var json = JsonExtensions.SerializeAndSelectTokens(root, paths, Formatting.Indented);        Console.WriteLine("Result using paths: {0}", JsonConvert.SerializeObject(paths));        Console.WriteLine(json);        Assert.IsTrue(JObject.Parse(json).DescendantsAndSelf().OfType<JValue>().Count() == expectedCount); // 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; }}

请注意,有一个增强功能请求:ADD
JsonProperty.ShouldSerialize(对象目标,字符串路径)#1857
,可以更轻松地启用这种功能。

演示在这里和这里摆弄。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5174617.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-18
下一篇 2022-11-18

发表评论

登录后才能评论

评论列表(0条)

保存