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

JFa*_*con 8 serialization json json.net partial

我有一个情况,我有一个非常大的C#对象,但是,我只需要返回一些属性(可以在嵌套对象上),允许客户端JavaScript修改这些属性,然后发送生成的对象返回服务器以执行就地部分反序列化.

我们的想法是重用一些非常大的现有业务对象,但要智能化,只能序列化并仅将这些属性发送回客户端应用程序进行修改(以便将传输的数据量保持在最低水平).

我基本上有一个XML文件,我使用"路径语法"预先定义所有绑定,这只表示我需要序列化的那些属性.所以,我可以使用"WorkOrder.UserField1"或"WorkOrder.Client.Name"之类的东西.

我尝试使用自定义合约解析程序来确定是否应该序列化属性; 但是,我似乎没有关于"路径"(换句话说,链中对象模型中的其他属性)的信息,以确定属性是否应该序列化.

我也尝试使用自定义的JsonTextWriter,但似乎我不能覆盖跟踪路径所需的方法,即使有可用的Path属性.为了能够查看被序列化的属性的路径层次结构并确定是否应该通过在表中查找路径并做出决定来序列化,是否有一些我可能会忽略的东西?

dbc*_*dbc 11

这里的基本困难是Json.NET是一个基于合同的序列化程序,它为每个要序列化的类型创建一个合同,然后根据合同(de)序列化.如果类型出现在对象层次结构中的多个位置,则应用相同的合同.但是,您希望根据层次结构中的位置选择性地包含给定类型的属性,这与基本的"一类合同"设计冲突.

解决此问题的一种快速方法是序列化为a JObject,然后使用它JToken.SelectTokens()来仅选择要返回的JSON数据,删除其他所有内容.由于SelectTokens完全支持"Bar"查询语法,因此您可以选择性地包括使用数组和属性通配符或其他过滤器,例如:

"$.FirstLevel[*].Bar"
Run Code Online (Sandbox Code Playgroud)

包括在根对象的"FirstLevel"名称属性的所有数组成员中命名的所有属性JObject.

这应该会根据需要减少网络使用量,但不会在服务器上节省任何处理时间.

可以使用以下扩展方法完成删除:

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 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();
        }
    }
}

/// <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
}
Run Code Online (Sandbox Code Playgroud)

然后使用它们像:

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; }
}
Run Code Online (Sandbox Code Playgroud)

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