当前位置: 首页 > 面试题库 >

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

温浩大
2023-03-14
问题内容

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

这个想法是重新使用一些非常大的现有业务对象,但是只对序列化并将那些属性仅发送回客户端应用程序进行修改(以使传输的数据量保持最小)是明智的。

我基本上有一个XML文件,在其中我使用“路径语法”预定义了所有绑定,该路径仅指示需要序列化的那些属性。因此,我可以使用“
WorkOrder.UserField1”或“ WorkOrder.Client.Name”之类的东西。

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

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


问题答案:

这里的基本困难是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
,可以更轻松地启用这种功能。

演示在这里和这里摆弄。



 类似资料:
  • 问题内容: 我有一个名为MyObject的对象,它具有多个属性。MyList是我用linq查询填充的MyObject列表,然后将MyList序列化为json。我最终得到这样的东西 我要做的是仅序列化MyObject的一部分。例如,我可能有Property1,Property2 … Propertyn,并且我希望MyJson仅包含Property3,Property5和Property8。 我想到了

  • 问题内容: 我正在使用Java 7和WatchService一起使用java.nio.file.WatchEvent。注册后,当我轮询ENTRY_MODIFY事件时,无法到达该事件文件的绝对路径。有什么方法可以从WatchEvent对象获取文件的绝对路径吗? 问题答案: 您需要从WatchKey获取父目录以解析完整路径 这段代码看起来像需要随附的文档一样,仅凭其本身就没有多大意义。他们对这种特定A

  • 问题内容: 这是我的Python3项目hiearchy: 从,我想运行提供该文件夹内容的http服务器。 这里提出这个代码来运行一个简单的HTTP服务器: 但其实这服务,而不是。如何指定要提供服务的文件夹的路径? 问题答案: https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler

  • 问题内容: 我有一个像这样的简单容器类的结构(在伪红宝石中): 有没有简单的方法可以在Ruby中将其反序列化为JSON?还是应该像本例一样为每个类制作嵌套的序列化方法? 编辑: 在我的特定情况下,我想将一些JSON数据发布到运行Ruby的服务器上,该服务器将提取数据并采取相应的措施。 JSON的发送者不一定是Ruby进程,而可能是其他系统的后端。(尽管在我的测试工具中是Ruby)。 因此,我不需要

  • 我正试图使用MacOS“scheduler”执行一些Java代码。 我的plist文件: 剧本上海: 问题是,我在程序中的所有相对路径(资源路径、属性、日志位置、Springbean文件等)都失败了。 plist文件位于根文件夹下,因此我假设所有路径都是相对于执行器位置的。

  • 我正试图在应用程序处于退出状态时从firebase保存一个通知到redux。 但是我需要在app.js中这样做,当我甚至不在组件上时,我如何分派redux操作呢? 这就是我要做的