如何在c#中找到json记录的所有不同键?

时间:2015-04-28 16:29:24

标签: c# json

为什么这个问题不重复? (看到评论后添加)

  • 它与Entity框架无关。
  • 它必须处理解析巨大的json文件并找到不同的密钥,但不是记录!

我有200多个文件,每个文件都是2 GB以上,意味着总大小为400多GB。这些文件中的每一行都是一个json字符串。我事先没有json schema的记录。我的工作是找到这些文件中的所有密钥。

我编写了以下代码来获取所有这些json记录中的所有不同键。我使用来自for-loop的多线程main来调用以下方法。

private void GetTokensFromJson(string filePath)
        {
            IEnumerable<string> txts = File.ReadLines(filePath, Encoding.UTF8);

            Console.WriteLine(txts.Count());

            List<string> distinctKeys = new List<string>();

            foreach (var text in txts)
            {

                    string pattern = "{\"";

                    foreach (Match m in Regex.Matches(text, pattern))
                    {
                        //string matchValue = m.Value;
                        int matchIndex = m.Index;
                        string subStr=text.Substring(matchIndex+2, text.Length - matchIndex - 3);
                        int quoteIndex=subStr.IndexOf('\"');
                        string jsonKey = subStr.Substring(0, quoteIndex);
                        if (!distinctKeys.Contains(jsonKey) && !jsonKey.Contains("\\"))
                        {
                            Console.WriteLine(jsonKey);
                            distinctKeys.Add(jsonKey);
                        }
                    }

                string secondPattern="\":";
                foreach (Match m in Regex.Matches(text, secondPattern))
                {
                    int matchIndex = m.Index;
                    string revJsonKKey = "";
                    while(matchIndex>0)
                    {
                        matchIndex--;
                        if (text[matchIndex] != '\"')
                            revJsonKKey += text[matchIndex];
                        else
                            break;
                    }

                    IEnumerable<char> jsonKeyCharArray = revJsonKKey.Reverse();
                    string jsonKey="";
                    foreach(char c in jsonKeyCharArray)
                    {
                        jsonKey += c;
                    }

                    if (!distinctKeys.Contains(jsonKey) && !jsonKey.Contains("\\"))
                    {
                        Console.WriteLine(jsonKey);
                        distinctKeys.Add(jsonKey);
                    }

                }

            }

distinctKeys拥有所有不同的json键。但我错过了几个键并添加了不需要的键,不知道为什么:|。我无法调试给定的输入,因为它太大了!此外,这种方法太慢了。

为了让事情更清楚,让我们举一个例子,如果文件有json,

{"id":"123", "name":"hello, world", "department":[{"name":"dept1", "deptID":"123"}]}
{"id":"456324", "department":[{"name":"dept2", "deptID":"456"}]}

预期输出id,name,department, department->name, department->deptID。输出格式无关紧要。请注意,并非所有json记录都没有所有密钥,json记录可以包含嵌套的json记录。

两个问题,

  1. 我在代码中做错了什么?
  2. 当我将输入作为复杂的json记录时,是否有内置或第三方dll会将输出作为json的键输出?

4 个答案:

答案 0 :(得分:3)

尝试使用Json.net,Path属性包含该对象的完整路径

 private static void GetKeys(JObject obj, List<string> keys)
    {
        var result = obj.Descendants()
            .Where(f => f is JProperty) //.Where(f => f is JProperty) 
            .Select(f => f as JProperty)// and .Select(f => f as JProperty) can be replaced with .OfType<JProperty>()
            .Select(f=>f.Path)
            .Where(f=> !keys.Contains(f));
        keys.AddRange(result);
    }

    static void Main(string[] args)
    {         
        IEnumerable<string> txts = @"{'id':'123', 'name':'hello, world',     'department':[{'name':'dept1', 'deptID':'123'}]}
{'id':'456324', 'department':[{'name':'dept2', 'deptID':'456'}]}".Split("\r\n".ToArray(),StringSplitOptions.RemoveEmptyEntries);
        List<string> keys = new List<string>();
        foreach (var item in txts)
        {
            var obj = JObject.Parse(item);
            GetKeys(obj, keys);
        }

}

答案 1 :(得分:1)

将字符串读入JSON.NET并将其转换为Jobjects

然后循环访问Jobjects

 foreach ( jobject in jobjects )   
{
 IList<string> keys = jobject .Properties().Select(p => p.Name).ToList();
}

然后做

keys.distinct();

就像

 private void GetTokensFromJson(string filePath)
            {
                IEnumerable<string> txts = File.ReadLines(filePath, Encoding.UTF8);
    List<JObject> jObjects = new List<JObject>() {};
    IList<string> keyslist;

                Console.WriteLine(txts.Count());

                List<string> distinctKeys = new List<string>();

                foreach (var text in txts)
                {

                      var obj = JObject.Parse(text); 
                      jObjects.add(obj);  


                }
    for each ( jobject in jobjects )   
    {
     IList<string> keys = jobject .Properties().Select(p => p.Name).ToList();
    keyslist.add(keys);
    }
    keyslist.distinct();

         }

答案 2 :(得分:1)

让我们做数学吧。你有:

  • 200个文件
  • 至少2 GB
  • 其中一行是,平均可以说120个字符(240个字节)

这使得400 GB内存仅用于保存所有内容和 1,789,569,707,即近2 十亿行。

显然,你的问题是不是与解析相关的问题,而是以增量方式管理内存和索引,使用并非全部驻留在内存中的中间结果。

使用您现在拥有的简单列表来跟踪您的密钥,并假设您的密钥中 1/20 是唯一的:

  • 您现在必须在索引列表中维护1.25亿个关键条目
  • 如果单个键索引条目所需的存储空间为80字节,则这将占用9 GB内存的列表。

搜索列表(1.25亿个项目)以获取新行的重复项将非常缓慢。

您可能需要查看map / reduce风格算法,以确定如何实现这样的效果。

答案 3 :(得分:1)

一些问题:

  1. 不要Console.WriteLine(txts.Count());。我相信这实际上会让你读两遍整个文件 - 一次计数,一次读取密钥。

  2. 使用HashSet<string>收集不同的密钥,它比使用列表快得多。

  3. 正如Kenner Dev建议的那样,安装Json.NET并使用LINQ to JSON来解析文件的每一行,而无需了解架构。

  4. 继续按照您目前的操作逐行阅读文件,不要尝试以任何方式一次性将整个内容加载到内存中。

  5. 然后,GetTokensFromJson变为:

        public static HashSet<string> GetTokensFromJson(IEnumerable<string> txts)
        {
            return new HashSet<string>(txts.Select(t => JObject.Parse(t)).Where(o => o != null).SelectMany(o => o.Descendants().OfType<JProperty>()).Select(p => p.Name));
        }