我有一个任意定义的JSON文档,我希望能够应用一个JSONPath表达式,就像属性的白名单过滤器:所有选定的节点及其祖先返回到根节点,所有其他节点都被删除。如果节点不存在,我应该得到一个空文档。
JSON. Net中似乎没有类似的东西,我在任何地方都找不到类似的例子,所以我建立了自己的例子。我选择将选定的节点复制到新构建的文档中,而不是尝试删除所有不匹配的节点。鉴于可能存在多个匹配项,并且文档可能很大,它需要能够有效地处理将多个选择结果合并到单个树/JSON文档中。
我的尝试有点效果,但我得到了奇怪的结果。该过程涉及一个MergedAnceery
方法,该方法迭代SelectTokens
结果,调用GetFullAnceery
(递归地将树构建到该节点),然后合并结果。不过,JArray的合并似乎发生在错误的级别,正如您在下面的“实际结果”中看到的那样。
我的问题是:
代码:
public static void Main()
{
string json = @"..."; // snipped for brevity - see DotNetFiddle: https://dotnetfiddle.net/wKN1Hj
var root = (JContainer)JToken.Parse(json);
var t3 = root.SelectTokens("$.Array3B.[*].Array3B1.[*].*");
// See DotNetFiddle for simpler examples that work
Console.WriteLine($"{MergedAncestry(t3).ToString()}"); // Wrong output!
Console.ReadKey();
}
// Returns a single document merged using the full ancestry of each of the input tokens
static JToken MergedAncestry(IEnumerable<JToken> tokens)
{
JObject merged = null;
foreach(var token in tokens)
{
if (merged == null)
{
// First object
merged = (JObject)GetFullAncestry(token);
}
else
{
// Subsequent objects merged
merged.Merge((JObject)GetFullAncestry(token), new JsonMergeSettings
{
// union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union
});
}
}
return merged ?? new JObject();
}
// Recursively builds a new tree to the node matching the ancestry of the original node
static JToken GetFullAncestry(JToken node, JToken tree = null)
{
if (tree == null)
{
// First level: start by cloning the current node
tree = node?.DeepClone();
}
if (node?.Parent == null)
{
// No parents left, return the tree we've built
return tree;
}
// Rebuild the parent node in our tree based on the type of node
JToken a;
switch (node.Parent)
{
case JArray _:
return GetFullAncestry(node.Parent, new JArray(tree));
case JProperty _:
return GetFullAncestry(node.Parent, new JProperty(((JProperty)node.Parent).Name, tree));
case JObject _:
return GetFullAncestry(node.Parent, new JObject(tree));
default:
return tree;
}
}
JSON示例:
{
"Array3A": [
{ "Item_3A1": "Desc_3A1" }
],
"Array3B": [
{ "Item_3B1": "Desc_3B1" },
{
"Array3B1": [
{ "Item_1": "Desc_3B11" },
{ "Item_2": "Desc_3B12" },
{ "Item_3": "Desc_3B13" }
]
},
{
"Array3B2": [
{ "Item_1": "Desc_3B21" },
{ "Item_2": "Desc_3B22" },
{ "Item_3": "Desc_3B23" }
]
}
]
}
有关完整代码和测试,请参阅DotNetFiddle
"过滤器"JSONPath:
$.Array3B.[*].Array3B1.[*].*
预期成果:
{
"Array3B": [
{
"Array3B1": [
{ "Item_1": "Desc_3B11" },
{ "Item_2": "Desc_3B12" },
{ "Item_3": "Desc_3B13" }
]
}
]
}
实际结果:
{
"Array3B": [
{
"Array3B1": [ { "Item_1": "Desc_3B11" } ]
},
{
"Array3B1": [ { "Item_2": "Desc_3B12" } ]
},
{
"Array3B1": [ { "Item_3": "Desc_3B13" } ]
}
]
}
好的,我找到了一个方法。感谢@dbc的建议、改进和指出问题。
递归最终不会很好地工作,因为我需要确保树中具有公共父级的同一级别上的所有节点都匹配,而任何级别上都可能有输入节点。
我添加了一个方法来对多个JSONPaths进行过滤,以输出单个结果文档,因为这是最初的目标。
static JToken FilterByJSONPath(JToken document, IEnumerable<string> jPaths)
{
var matches = jPaths.SelectMany(path => document.SelectTokens(path, false));
return MergeAncestry(matches);
}
static JToken MergeAncestry(IEnumerable<JToken> tokens)
{
if (tokens == null || !tokens.Any())
{
return new JObject();
}
// Get a dictionary of tokens indexed by their depth
var tokensByDepth = tokens
.Distinct(ObjectReferenceEqualityComparer<JToken>.Default)
.GroupBy(t => t.Ancestors().Count())
.ToDictionary(
g => g.Key,
g => g.Select(node => new CarbonCopyToken { Original = node, CarbonCopy = node.DeepClone() })
.ToList());
// start at the deepest level working up
int depth = tokensByDepth.Keys.Max();
for (int i = depth; i > 0; i--)
{
// If there's nothing at the next level up, create a list to hold parents of children at this level
if (!tokensByDepth.ContainsKey(i - 1))
{
tokensByDepth.Add(i - 1, new List<CarbonCopyToken>());
}
// Merge all tokens at this level into families by common parent
foreach (var parent in MergeCommonParents(tokensByDepth[i]))
{
tokensByDepth[i - 1].Add(parent);
}
}
// we should be left with a list containing a single CarbonCopyToken - contining the root of our copied document and the root of the source
var cc = tokensByDepth[0].FirstOrDefault();
return cc?.CarbonCopy ?? new JObject();
}
static IEnumerable<CarbonCopyToken> MergeCommonParents(IEnumerable<CarbonCopyToken> tokens)
{
var newParents = tokens.GroupBy(t => t.Original.Parent).Select(g => new CarbonCopyToken {
Original = g.First().Original.Parent,
CarbonCopy = CopyCommonParent(g.First().Original.Parent, g.AsEnumerable())
});
return newParents;
}
static JToken CopyCommonParent(JToken parent, IEnumerable<CarbonCopyToken> children)
{
switch (parent)
{
case JProperty _:
return new JProperty(((JProperty)parent).Name, children.First().CarbonCopy);
case JArray _:
var newParentArray = new JArray();
foreach (var child in children)
{
newParentArray.Add(child.CarbonCopy);
}
return newParentArray;
default: // JObject, or any other type we don't recognise
var newParentObject = new JObject();
foreach (var child in children)
{
newParentObject.Add(child.CarbonCopy);
}
return newParentObject;
}
}
请注意,它使用了几个新类:CarbonCopyToken
允许我们在逐级处理树时跟踪节点及其副本,以及ObjectReassiceEqualityCompler
public class CarbonCopyToken
{
public JToken Original { get; set; }
public JToken CarbonCopy { get; set; }
}
/// <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
}
用法示例:
List<string> filters = new {
"$..Test1",
"$.Path.To.[*].Some.Nodes",
"$.Other.*.Nodes"
}
var result = FilterByJSONPath(inputDocument, filters);
DotNetFiddle显示以前的测试和一个额外的测试:https://dotnetfiddle.net/ekABRI
问题内容: 嘿,我想知道是否有人知道使用正则表达式或通配符(或SQL中的pehaps )的方式,以便可以使用JSONPath在大量JSON数据内进行搜索。 例如(是的,我正在解析,而不是在应用程序中读取数据): 我希望能够浏览这样的数据: 其中参数的内容是数据对中部分或全部值的一部分。 目前,我只找到文件上,,,和关系运算符,它不给我那么多的灵活性。 有谁知道一个方法可以让我只是 刚刚 JSONP
我正在尝试使用 Jolt 转换来转换 Json,在这里寻找一些输入。我正在尝试过滤一个键,该键是另一个属性的值。这是我的输入和预期输出 我看到的输出是 我试过的规格是 但是我没有得到预期的输出。我也尝试了一些其他组合,但未能获得正确的输出。有人能帮忙吗?
我有一个,我希望用户输入一个人的姓名。我认为名称应该包含、和示例。我正在使用来验证用户输入。但是,我不知道如何在我的中设置它。 问题:我应该如何修改我的过滤器来实现上述行为? 任何关于如何验证一个人的名字的建议都被接受。 这是我的DocumentFilter: 这是我的测试类:
考虑以下Firestore结构: 收藏 现在,我想查询宠物满足某些条件的人。例如,拥有两只以上宠物的人,或者拥有一只名为“ABC”的宠物的人。 这可能吗?
问题内容: 我在子文档中有这样的数组 我可以过滤> 3的子文档吗 我的预期结果如下 我尝试使用,$elemMatch但返回数组中的第一个匹配元素 我的查询: 结果返回数组中的一个元素 我尝试使用聚合与$match但不起作用 返回数组中的所有元素 我可以过滤数组中的元素以获得预期结果吗? 问题答案: 使用是正确的方法,但在应用数组之前需要先对数组进行过滤,以便可以过滤单个元素,然后用于将其放回原处:
我想知道是否有办法在过滤掉elasticsearch文档后更新它们。 假设我有一个包含以下文档的用户集合: 现在我需要做的是更新所有30岁以上用户的名字。查看大量文档并在谷歌上搜索数小时,包括以下文档http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_updating_documents.html 我找不到办