首先,我非常感谢this https://stackoverflow.com/a/70080357/876743 and this https://stackoverflow.com/a/30333562/876743答案。它们成为我分析问题的起点。
这些答案提出了两种不同的方法来实现按路径“白名单”的目标。这first https://stackoverflow.com/a/70080357/876743一种从头开始重建白名单路径结构(即从空对象开始创建所需的路由)。该实现解析字符串路径并尝试根据解析的路径重建树。这种方法需要非常方便地考虑所有可能的路径类型,因此可能容易出错。你可以在我的评论中找到我发现的一些错误answer https://stackoverflow.com/a/70080357/876743.
The second https://stackoverflow.com/a/30333562/876743方法基于 json.net 对象树 API(Parent、Ancestors、Descendants 等)。该算法遍历树并删除未“列入白名单”的路径。我发现这种方法更容易,更不容易出错,并且“一次性”支持广泛的案例。
我实现的算法在很多方面与second https://stackoverflow.com/a/30333562/876743答案,但我认为,实施和理解要容易得多。另外,我不认为它的性能更差。
public static class JsonExtensions
{
public static TJToken RemoveAllExcept<TJToken>(this TJToken token, IEnumerable<string> paths) where TJToken : JContainer
{
HashSet<JToken> nodesToRemove = new(ReferenceEqualityComparer.Instance);
HashSet<JToken> nodesToKeep = new(ReferenceEqualityComparer.Instance);
foreach (var whitelistedToken in paths.SelectMany(token.SelectTokens))
TraverseTokenPath(whitelistedToken, nodesToRemove, nodesToKeep);
//In that case neither path from paths has returned any token
if (nodesToKeep.Count == 0)
{
token.RemoveAll();
return token;
}
nodesToRemove.ExceptWith(nodesToKeep);
foreach (var notWhitelistedNode in nodesToRemove)
notWhitelistedNode.Remove();
return token;
}
private static void TraverseTokenPath(JToken value, ISet<JToken> nodesToRemove, ISet<JToken> nodesToKeep)
{
JToken? immediateValue = value;
do
{
nodesToKeep.Add(immediateValue);
if (immediateValue.Parent is JObject or JArray)
{
foreach (var child in immediateValue.Parent.Children())
if (!ReferenceEqualityComparer.Instance.Equals(child, value))
nodesToRemove.Add(child);
}
immediateValue = immediateValue.Parent;
} while (immediateValue != null);
}
}
要比较 JToken 实例,必须使用引用相等比较器,因为某些 JToken 类型像 JValue 一样使用“按值”比较。否则,在某些情况下您可能会出现错误行为。
例如,有源 JSON
{
"path2":{
"path2Inner2":[
"id",
"id"
]
}
}
和一条路$..path2Inner2[0]
你会得到结果 JSON
{
"path2":{
"path2Inner2":[
"id",
"id"
]
}
}
代替
{
"path2":{
"path2Inner2":[
"id"
]
}
}
就 .net 5.0 而言,标准ReferenceEqualityComparer
可以使用。如果您使用早期版本的 .net,您可能需要实施它 https://stackoverflow.com/a/35520207/876743.