LINQ to JSON 数组查询

Posted

技术标签:

【中文标题】LINQ to JSON 数组查询【英文标题】:LINQ to JSON group query on array 【发布时间】:2020-01-04 01:11:56 【问题描述】:

我有一个 JSON 数据示例,我正在使用 NewtonSoft 将其转换为 JArray。

        string jsonString = @"['features': ['sunroof','mag wheels'],'features': ['sunroof'],'features': ['mag wheels'],'features': ['sunroof','mag wheels','spoiler'],'features': ['sunroof','spoiler'],'features': ['sunroof','mag wheels'],'features': ['spoiler']]";

我正在尝试检索最常一起请求的功能。基于上述数据集,我的预期输出将是:

天窗,磁轮,2 天窗, 1 磁轮 1 天窗、磁轮、扰流板、1 天窗,扰流板,1 扰流板,1

但是,我的 LINQ 生锈了,我用来查询 JSON 数据的代码返回的是单个功能的计数,而不是一起选择的功能:

        JArray autoFeatures = JArray.Parse(jsonString);
        var features = from f in autoFeatures.Select(feat => feat["features"]).Values<string>()
                       group f by f into grp
                       orderby grp.Count() descending
                       select new  indFeature = grp.Key, count = grp.Count() ;

        foreach (var feature in features)
        
            Console.WriteLine("0, 1", feature.indFeature, feature.count);
        

实际输出: 天窗,5 磁轮,4 扰流板,3

我在想也许我的查询需要一个“不同的”,但我不确定。

【问题讨论】:

var features = JsonConvert.DeserializeObject&lt;List&lt;Dictionary&lt;string, string[]&gt;&gt;&gt;(jsonString).SelectMany(d =&gt; d).GroupBy(k =&gt; string.Concat(k.Value.OrderBy(s =&gt; s))).Select(g =&gt; new Feature = g.Key, Count = g.Count() ).OrderByDescending(a =&gt; a.Count);。值字符串在内部预先排序(以生成忽略字符串值位置的有序组) 【参考方案1】:

这是 Select 的问题。您告诉它让在数组中找到的每个值都成为它自己的项目。实际上,您需要将所有值组合成每个特征的字符串。这是你的做法

var features = from f in autoFeatures.Select(feat => string.Join(",",feat["features"].Values<string>()))
                       group f by f into grp
                       orderby grp.Count() descending
                       select new  indFeature = grp.Key, count = grp.Count() ;

产生以下输出

sunroof,mag wheels, 2
sunroof, 1
mag wheels, 1
sunroof,mag wheels,spoiler, 1
sunroof,spoiler, 1
spoiler, 1

【讨论】:

这正是我所需要的。谢谢。我没有意识到我必须将这些字符串连接在一起。我想有一种方法可以在不进行字符串操作的情况下提取该信息。 使用 LINQ 很可能有办法做到这一点,但它远远超出了我的能力范围!虽然希望现在我已经发布了答案,但专家们会站出来展示我的版本是多么错误和低效,我们都可以学到一些东西:)【参考方案2】:

您可以使用HashSet 来识别不同的功能集,并对这些集进行分组。这样一来,您的 Linq 看起来与您现在拥有的基本相同,但您需要在 GroupBy 中添加一个 IEqualityComparer 类来帮助将一组功能与另一组功能进行比较,以检查它们是否相同。

例如:

var featureSets = autoFeatures
    .Select(feature => new HashSet<string>(feature["features"].Values<string>()))
    .GroupBy(a => a, new HashSetComparer<string>())
    .Select(a => new  Set = a.Key, Count = a.Count() )
    .OrderByDescending(a => a.Count);

foreach (var result in featureSets)

    Console.WriteLine($"String.Join(",", result.Set): result.Count");

比较器类利用HashSet 类的SetEquals 方法来检查一组是否与另一组相同(这会处理字符串在组中的不同顺序等)

public class HashSetComparer<T> : IEqualityComparer<HashSet<T>>

    public bool Equals(HashSet<T> x, HashSet<T> y)
    
        // so if x and y both contain "sunroof" only, this is true 
        // even if x and y are a different instance
        return x.SetEquals(y);
    

    public int GetHashCode(HashSet<T> obj)
    
        // force comparison every time by always returning the same, 
        // or we could do something smarter like hash the contents
        return 0; 
    

【讨论】:

刚刚在更复杂的数据集上尝试了您的解决方案。 HashSetComparer 对于捕获和组合(分组)功能未按相同顺序列出的情况至关重要。谢谢。 当然,这是一个很好的考虑,但我不太确定这是对这个问题的一个很好的答案。因为,似乎提出这种考虑的好处在于它没有成为问题本身的一部分。此外,提出这一考虑会引发更多考虑:这些功能的开始顺序和/或这些功能开始的顺序是否有任何意义。如果您可能希望“天窗,磁轮”和“磁轮,天窗”难以区分,那么您不会使用这种方法;如果您已经可以订购features,则不需要这种方法。 我不想过多剖析这个深思熟虑的答案,但如果有人认为这是最好的方法......这取决于。 根据我的问题“按要求”,这有点矫枉过正。根据我的实际需求和数据集,这个答案是绝对有必要的。

以上是关于LINQ to JSON 数组查询的主要内容,如果未能解决你的问题,请参考以下文章

Linq to Object之非延迟标准查询操作符

是否可以为 linq-to-objects 编译查询

是否可以为 linq-to-objects 编译查询

Linq to SQL 的连表查询(转)

linq-to-sql 是不是处理动态查询?

为啥 LINQ-to-Entities 将此查询放在子选择中?