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

时间:2015-05-18 13:09:45

标签: json serialization json.net partial

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

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

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

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

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

1 个答案:

答案 0 :(得分:10)

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

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

"$.FirstLevel[*].Bar"

包含根对象的名为"Bar"的属性的所有数组成员中名为"FirstLevel"的所有属性。

这会减少您的网络使用量,但不会在服务器上保存任何处理时间。

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

public static class JsonExtensions
{
    public static JObject 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();

        // Return the object itself for fluent style programming.
        return obj;
    }

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

请注意,有一个增强请求Feature request: ADD JsonProperty.ShouldSerialize(object target, string path) #1857可以更轻松地启用此类功能。