分割大型JSON文件的策略

我正在尝试将非常大的JSON文件拆分为给定数组的较小文件。 例如:

{ "headerName1": "headerVal1", "headerName2": "headerVal2", "headerName3": [{ "element1Name1": "element1Value1" }, { "element2Name1": "element2Value1" }, { "element3Name1": "element3Value1" }, { "element4Name1": "element4Value1" }, { "element5Name1": "element5Value1" }, { "element6Name1": "element6Value1" }] } 

…向下到{“elementNName1”:“elementNValue1”}其中N是一个大数字

用户提供表示要拆分的数组的名称(在此示例中为“headerName3”)和每个文件的数组对象数,例如1,000,000

这将导致N个文件各自包含顶部名称:值对(headerName1,headerName3)和每个文件中最多1,000,000个headerName3对象。

我正在使用优秀的Newtonsof JSON.net,并了解我需要使用流来完成此操作。

到目前为止,我已经查看了JToken对象中的一个读数,以确定在读取标记时PropertyName ==“headerName3”的位置,但我想要做的是在整个JSON对象中读取数组中的每个对象而不是继续将JSON解析为JTokens;

这是我到目前为止构建的代码片段:

  using (StreamReader oSR = File.OpenText(strInput)) { using (var reader = new JsonTextReader(oSR)) { while (reader.Read()) { if (reader.TokenType == JsonToken.StartObject) { intObjectCount++; } else if (reader.TokenType == JsonToken.EndObject) { intObjectCount--; if (intObjectCount == 1) { intArrayRecordCount++; // Here I want to read the entire object for this record into an untyped JSON object if( intArrayRecordCount % 1000000 == 0) { //write these to the split file } } } } } } 

我不知道 – 事实上,并不关心 – JSON本身的结构,并且对象可以在数组中具有不同的结构。 因此,我没有序列化课程。

这是正确的方法吗? JSON.net库中是否有一组方法可以轻松用于执行此类操作?

任何帮助赞赏。

您可以使用JsonWriter.WriteToken(JsonReader reader, true)将单个数组条目及其后代从JsonReader流式JsonReaderJsonWriter 。 您还可以使用JProperty.Load(JsonReader reader)JProperty.WriteTo(JsonWriter writer)来读取和写入整个属性及其后代。

使用这些方法,您可以创建一个状态机来解析JSON文件,遍历根对象,加载“前缀”和“后缀”属性,拆分数组属性,并将前缀,数组切片和后缀属性写入新文件。

这是一个原型实现,它采用TextReader和回调函数为分割文件创建顺序输出TextWriter对象:

  enum SplitState { InPrefix, InSplitProperty, InSplitArray, InPostfix, } public static void SplitJson(TextReader textReader, string tokenName, long maxItems, Func createStream, Formatting formatting) { List prefixProperties = new List(); List postFixProperties = new List(); List writers = new List(); SplitState state = SplitState.InPrefix; long count = 0; try { using (var reader = new JsonTextReader(textReader)) { bool doRead = true; while (doRead ? reader.Read() : true) { doRead = true; if (reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) continue; if (reader.Depth == 0) { if (reader.TokenType != JsonToken.StartObject && reader.TokenType != JsonToken.EndObject) throw new JsonException("JSON root container is not an Object"); } else if (reader.Depth == 1 && reader.TokenType == JsonToken.PropertyName) { if ((string)reader.Value == tokenName) { state = SplitState.InSplitProperty; } else { if (state == SplitState.InSplitProperty) state = SplitState.InPostfix; var property = JProperty.Load(reader); doRead = false; // JProperty.Load() will have already advanced the reader. if (state == SplitState.InPrefix) { prefixProperties.Add(property); } else { postFixProperties.Add(property); } } } else if (reader.Depth == 1 && reader.TokenType == JsonToken.StartArray && state == SplitState.InSplitProperty) { state = SplitState.InSplitArray; } else if (reader.Depth == 1 && reader.TokenType == JsonToken.EndArray && state == SplitState.InSplitArray) { state = SplitState.InSplitProperty; } else if (state == SplitState.InSplitArray && reader.Depth == 2) { if (count % maxItems == 0) { var writer = new JsonTextWriter(createStream(writers.Count)) { Formatting = formatting }; writers.Add(writer); writer.WriteStartObject(); foreach (var property in prefixProperties) property.WriteTo(writer); writer.WritePropertyName(tokenName); writer.WriteStartArray(); } count++; writers.Last().WriteToken(reader, true); } else { throw new JsonException("Internal error"); } } } foreach (var writer in writers) using (writer) { writer.WriteEndArray(); foreach (var property in postFixProperties) property.WriteTo(writer); writer.WriteEndObject(); } } finally { // Make sure files are closed in the event of an exception. foreach (var writer in writers) using (writer) { } } } 

此方法将所有文件保持打开状态直到结束,以防出现在数组属性之后的“postfix”属性需要追加。 请注意,一次打开文件的数量限制为16384 ,因此如果您需要创建更多拆分文件,则无法使用。 如果在实践中从未遇到过postfix属性,则可以在打开下一个文件之前关闭每个文件,并在发现任何postfix属性时抛出exception。 否则,您可能需要在两次传递中解析大文件或关闭并重新打开拆分文件以附加它们。

以下是如何将该方法与内存中的JSON字符串一起使用的示例:

  private static void TestSplitJson(string json, string tokenName) { var builders = new List(); using (var reader = new StringReader(json)) { SplitJson(reader, tokenName, 2, i => { builders.Add(new StringBuilder()); return new StringWriter(builders.Last()); }, Formatting.Indented); } foreach (var s in builders.Select(b => b.ToString())) { Console.WriteLine(s); } } 

原型小提琴 。