2015-06-12 5 views
0

У меня есть приложение, в котором вход был увеличен с 50K записей местоположения до 1,1 миллиона записей местоположения. Это вызвало серьезные проблемы, так как весь файл был ранее де-сериализован в один объект. Размер объекта ~ 1 ГБ для производства, такого как файл с 1,1 миллионами записей. Из-за проблем с большими объектами GC я хочу сохранить де-сериализованный объект под отметкой 85K.Проблемы с обработкой 1GB json-файла с использованием JSON.NET

Я пытаюсь разобрать один объект местоположения за один раз и де-сериализовать его, чтобы я мог контролировать количество объектов , которые де-сериализуются и, в свою очередь, управляют размером объекта. Я использую библиотеки Json.Net для этого.

Ниже приведен образец файла JSON, который я получаю в качестве потока в свое приложение.

{ 
    "Locations": [{ 
     "LocationId": "", 
     "ParentLocationId": "", 
     "DisplayFlag": "Y", 
     "DisplayOptions": "", 
     "DisplayName": "", 
     "Address": "", 
     "SecondaryAddress": "", 
     "City": "", 
     "State": "", 
     "PostalCode": "", 
     "Country": "", 
     "Latitude": 40.59485, 
     "Longitude": -73.96174, 
     "LatLonQuality": 99, 
     "BusinessLogoUrl": "", 
     "BusinessUrl": "", 
     "DisplayText": "", 
     "PhoneNumber": "", 
     "VenueGroup": 7, 
     "VenueType": 0, 
     "SubVenue": 0, 
     "IndoorFlag": "", 
     "OperatorDefined": "", 
     "AccessPoints": [{ 
      "AccessPointId": "", 
      "MACAddress": "", 
      "DisplayFlag": "", 
      "DisplayOptions": "", 
      "Latitude": 40.59485, 
      "Longitude": -73.96174, 
      "Status": "Up", 
      "OperatorDefined": "", 
      "RoamingGroups": [{ 
       "GroupName": "" 
      }, 
      { 
       "GroupName": "" 
      }], 
      "Radios": [{ 
       "RadioId": "", 
       "RadioFrequency": "", 
       "RadioProtocols": [{ 
        "Protocol": "" 
       }], 
       "WifiConnections": [{ 
        "BSSID": "", 
        "ServiceSets": [{ 
         "SSID": "", 
         "SSID_Broadcasted": "" 
        }] 
       }] 
      }] 
     }] 
    }, 
    { 
     "LocationId": "", 
     "ParentLocationId": "", 
     "DisplayFlag": "Y", 
     "DisplayOptions": "", 
     "DisplayName": "", 
     "Address": "", 
     "SecondaryAddress": "", 
     "City": "", 
     "State": "", 
     "PostalCode": "", 
     "Country": "", 
     "Latitude": 40.59485, 
     "Longitude": -73.96174, 
     "LatLonQuality": 99, 
     "BusinessLogoUrl": "", 
     "BusinessUrl": "", 
     "DisplayText": "", 
     "PhoneNumber": "", 
     "VenueGroup": 7, 
     "VenueType": 0, 
     "SubVenue": 0, 
     "IndoorFlag": "", 
     "OperatorDefined": "", 
     "AccessPoints": [{ 
      "AccessPointId": "", 
      "MACAddress": "", 
      "DisplayFlag": "", 
      "DisplayOptions": "", 
      "Latitude": 40.59485, 
      "Longitude": -73.96174, 
      "Status": "Up", 
      "OperatorDefined": "", 
      "RoamingGroups": [{ 
       "GroupName": "" 
      }, 
      { 
       "GroupName": "" 
      }], 
      "Radios": [{ 
       "RadioId": "", 
       "RadioFrequency": "", 
       "RadioProtocols": [{ 
        "Protocol": "" 
       }], 
       "WifiConnections": [{ 
        "BSSID": "", 
        "ServiceSets": [{ 
         "SSID": "", 
         "SSID_Broadcasted": "" 
        }] 
       }] 
      }] 
     }] 
    }] 
} 

мне нужно, чтобы быть в состоянии вытащить отдельные объекты Места, так что я смотрел бы на следующий

{ 
    "LocationId": "", 
    "ParentLocationId": "", 
    "DisplayFlag": "Y", 
    "DisplayOptions": "", 
    "DisplayName": "", 
    "Address": "", 
    "SecondaryAddress": "", 
    "City": "", 
    "State": "", 
    "PostalCode": "", 
    "Country": "", 
    "Latitude": 40.59485, 
    "Longitude": -73.96174, 
    "LatLonQuality": 99, 
    "BusinessLogoUrl": "", 
    "BusinessUrl": "", 
    "DisplayText": "", 
    "PhoneNumber": "", 
    "VenueGroup": 7, 
    "VenueType": 0, 
    "SubVenue": 0, 
    "IndoorFlag": "", 
    "OperatorDefined": "", 
    "AccessPoints": [{ 
     "AccessPointId": "", 
     "MACAddress": "", 
     "DisplayFlag": "", 
     "DisplayOptions": "", 
     "Latitude": 40.59485, 
     "Longitude": -73.96174, 
     "Status": "Up", 
     "OperatorDefined": "", 
     "RoamingGroups": [{ 
      "GroupName": "" 
     }, 
     { 
      "GroupName": "" 
     }], 
     "Radios": [{ 
      "RadioId": "", 
      "RadioFrequency": "", 
      "RadioProtocols": [{ 
       "Protocol": "" 
      }], 
      "WifiConnections": [{ 
       "BSSID": "", 
       "ServiceSets": [{ 
        "SSID": "", 
        "SSID_Broadcasted": "" 
       }] 
      }] 
     }] 
    }] 
} 

Я пытаюсь использовать Json.NET JsonTextReader для достижения этой цели , однако я не могу заставить читателя содержать все место в своем буфере из-за размера записей в потоке, который первоначально читатель будет иметь до «RadioProtocols», который находится в середине пути через объект, к тому времени поток достигает конца объекта, читатель отбросил начало объекта.

код, я использую, чтобы попытаться получить эту функцию для работы

var ser = new JsonSerializer(); 
using (var reader = new JsonTextReader(new StreamReader(stream))) 
{ 
    reader.SupportMultipleContent = true; 

    while (reader.Read()) 
    { 
     if (reader.TokenType == JsonToken.StartObject && reader.Depth == 2) 
     {        
      do 
      { 
       reader.Read();         
      } while (reader.TokenType != JsonToken.EndObject && reader.Depth == 2); 

      var singleLocation = ser.Deserialize<Locations>(reader); 
     } 
    } 
} 

Любая информация об этом или альтернатива делать это было бы весьма признателен. В качестве дополнительной заметки способ, которым наши клиенты отправляют информацию, в настоящее время не может измениться.

+0

Это звучит, как вы собираетесь придется свернуть свой собственный сериалайзер, потому что маленький разумный блок, который json.NET JSON собирается десериализации заставит вас 'OutOfMemoryException'. При этом я считаю, что это совершенно неправильный подход. Я бы рассмотрел более серьезную проблему, которая является вашим явно вашим громоздким источником данных или недостаточным оборудованием. – evanmcdonnal

+0

К сожалению, мы не можем изменить подход в это время, нам в основном сказали исправлять только или, точнее, «просто заставить его работать, не меняя слишком много». – polydegmon

+0

Я попытался запустить ваш код, но я нашел проблему. Предполагая, что тип «Locations» соответствует записи в массиве «Locations», код генерирует исключение, потому что читатель неправильно расположен в свойстве «LocationId». Является ли идея перечислить каждую запись в массиве 'Locations', загружая каждый из них отдельно? – dbc

ответ

0

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

Если объект преобразован в объект JObject, он будет считываться в полном объекте и де-сериализовать его, это может быть закодировано, чтобы получить решение.

Это код, который был решен на

while (reader.Read()) 
{ 
    if (reader.TokenType == JsonToken.StartObject && reader.Depth == 2) 
    { 
     location = JObject.Load(reader).ToObject<Location>(); 

     var lv = new LocationValidator(location, FootprintInfo.OperatorId, FootprintInfo.RoamingGroups, true); 
     var vr = lv.IsValid(); 
     if (vr.Successful) 
     { 
      yield return location; 
     } 
     else 
     { 
      errors.Add(new Error(elNumber, location.LocationId, vr.Error.Field, vr.Error.Detail)); 
      if (errors.Count >= maxErrors) 
      { 
       yield break; 
      } 
     } 

     ++elNumber; 
    } 
} 
0

Когда читатель позиционируется в начале объекта, который вы хотите десериализовать (запись в массиве Locations в вашем случае), вы можете просто позвонить ser.Deserialize<T>(reader), и он будет работать, продвигаясь к концу объекта при этом уровень, и не более. Таким образом, следующее должно перебирать Location объектов в файле, загрузка каждого из них по отдельности:

public static IEnumerable<T> DeserializeNestedItems<T>(TextReader textReader) 
    { 
     var ser = new JsonSerializer(); 
     using (var reader = new JsonTextReader(textReader)) 
     { 
      reader.SupportMultipleContent = true; 

      while (reader.Read()) 
      { 
       if (reader.TokenType == JsonToken.StartObject && reader.Depth == 2) 
       { 
        var item = ser.Deserialize<T>(reader); 
        yield return item; 
       } 
      } 
     } 
    } 

И пример использования, используя тестовую строку:

 Debug.Assert(DeserializeNestedItems<Location>(new StringReader(json)).Count() == 2); // No assert. 

     var list = DeserializeNestedItems<Location>(new StringReader(json)).SelectMany(l => l.AccessPoints).Select(a => new { a.Latitude, a.Longitude }).ToList(); 

     Debug.WriteLine(JsonConvert.SerializeObject(list, Formatting.Indented)); 

который выводит:

[ 
    { 
    "Latitude": 40.59485, 
    "Longitude": -73.96174 
    }, 
    { 
    "Latitude": 40.59485, 
    "Longitude": -73.96174 
    } 
] 

Примечание - Location класс приходит f rom, разместив ваш JSON до http://json2csharp.com/.

+0

@polydegmon - Я не уверен на 100%, это отвечает на ваш вопрос. Если отдельный объект 'Location' не будет помещаться в память, это не поможет. Но из вашего кода вам может быть трудно разобрать JSON. – dbc