2015-04-28 1 views
1

Почему этот вопрос не дублируется? (Добавлено после просмотра комментариев)Как найти все отдельные ключи json-записей в C#?

  • Не имеет отношения к структуре Entity.
  • Он должен разбирать огромные файлы json и находить разные ключи, но не записи!

У меня есть 200+ файлов, и каждый из них - 2+ ГБ, подразумевает общий размер 400+ ГБ. Каждая строка в этих файлах представляет собой строку 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. Есть ли встроенная или сторонняя dll, которая даст мне выход в качестве ключей json, когда я даю ввод как сложную запись json?
+0

У вас есть два вопроса в одном, и я боюсь, что оба они могут сигнализировать все это как не по теме вопрос. _why не этот код работает? _ и _recommend или найти книгу, инструмент, библиотеку программного обеспечения, учебник или другой ресурс вне сайта. – melancia

+0

Я думаю, вы понимаете мои намерения :) Можете ли вы помочь мне в решении вопроса? В любом случае, код работает, но имеет ошибку: | – Abhishek

+0

Это не помогает моему делу, так как мне нужно разбирать json здесь и в сущности, вы знаете ключи! Вы должны просто сделать разный. Я не хочу найти разные записи. – Abhishek

ответ

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); 
     } 

}

+0

, попробовав его. Дайте мне некоторое время :) – Abhishek

+0

Awesome !! Upvoted. Спасибо, он работает как ожидалось !! Не возражаете ли вы объяснить запрос LINQ? Мне непонятно, как это происходит со всеми потомками. – Abhishek

+2

'Где (f => f - JProperty)' уничтожит все объекты, которые не являются свойством; 'Select (f => f as JProperty)' будет вызывать каждый анонимный дочерний объект 'f' в экземпляр класса JProperty; 'Select (f => f.Path)' будет «преобразовывать» каждый из этих экземпляров в строку («Путь»); последний 'Where' предотвратит добавление дубликатов в список' keys'. Вы также можете избежать последнего 'Where', используя' HashSet 'вместо' List', поскольку он не позволит дублировать в первую очередь. – easuter

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(); 

     } 
+0

Что такое 'json.net'? Любой образец, который принимает строку json и возвращает JObject без меня, дает схему json? – Abhishek

+0

http://www.newtonsoft.com/json –

+0

[Json.NET] (http://www.newtonsoft.com/json) - очень популярная библиотека для работы с 'JSON' на' C#'. Вы можете сериализовать/десериализовать данные, не зная схемы заранее. – melancia

1

Давайте делать математику должны мы.У вас есть:

  • 200 файлов
  • по крайней мере, 2 Гб
  • , где линия, позволяет сказать, что 120 символов (240 байт) в среднем

Это делает для Гб внутренней памяти 400 только для содержания всего контента и для 1,789,569,707, то есть почти 2 млрд. линий.

Очевидно, что ваша проблема здесь не, которая связана с разбором, а для управления вашей памятью и индексирования клавиш поэтапно, используя промежуточные результаты, которые не все находятся в памяти.

Используя простой список, что у вас есть сейчас, чтобы отслеживать свои ключи, и предполагая, что 1 в 20 ваших ключей уникален:

  • Теперь вы должны поддерживать 125 миллионов ключевых записей в списке индекса
  • Если хранилище, необходимое для записи одного ключевого индекса, составляет 80 байт, это будет содержать список, содержащий 9 ГБ памяти.

Поиск в списке (125 миллионов наименований) для дубликатов новой линии будет очень медленно.

Возможно, вы захотите изучить алгоритмы стиля карты/уменьшения, чтобы выяснить, как это может быть достигнуто.

+0

Я не храню все файлы сразу в памяти. Я возьму одну строку/файл за раз :). Отдельные клавиши не занимают столько памяти. – Abhishek

+1

Я согласен с @Alex –

+0

ok, позволяет предположить, что одна пятая ваших ключей уникальна, и чтобы сохранить индекс в памяти, вам нужно 80 байт на ключ в индексе, тогда ваш индекс будет 27 ГБ. Вы не можете держать это в простом списке в памяти. Сколько времени, по вашему мнению, потребуется для поиска по 500 миллионам элементов индекса, чтобы проверить, существует ли ключ для новой строки? – Alex

1

Несколько вопросов:

  1. Не делайте Console.WriteLine(txts.Count());. Я считаю, что это фактически заставляет вас читать весь файл дважды - один раз для подсчета и один раз читать ключи.

  2. Использовать HashSet<string> для сбора различных ключей, это намного быстрее, чем использование списка.

  3. Как Кеннер Дев предлагает установить Json.NET и использовать LINQ to JSON для анализа каждой строки файла без необходимости знать схему.

  4. Продолжайте читать файлы по очереди, как вы сейчас делаете, не пытайтесь загрузить всю вещь в памяти сразу в любом представлении.

Затем 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)); 
    } 
Смежные вопросы