我正在尝试将给定数组的非常大的 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 对象中的读取,以确定读取 token 时 PropertyName == "headerName3"发生的位置,但我想做的是读取数组中每个对象的整个 JSON 对象并且不必继续将 JSON 解析为 JToken;
这是我迄今为止正在构建的代码片段:
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
流式传输各个数组条目及其后代到 JsonWriter
。您还可以使用JProperty.Load(JsonReader reader)
和 JProperty.WriteTo(JsonWriter writer)
读取和写入整个属性及其后代。
使用这些方法,您可以创建一个状态机,用于解析 JSON 文件、迭代根对象、加载“前缀”和“后缀”属性、拆分数组属性以及写入前缀、数组切片和后缀属性输出到新文件。
下面是一个原型(prototype)实现,它采用 TextReader
和回调函数来为拆分文件创建顺序输出 TextWriter
对象:
enum SplitState
{
InPrefix,
InSplitProperty,
InSplitArray,
InPostfix,
}
public static void SplitJson(TextReader textReader, string tokenName, long maxItems, Func<int, TextWriter> createStream, Formatting formatting)
{
List<JProperty> prefixProperties = new List<JProperty>();
List<JProperty> postFixProperties = new List<JProperty>();
List<JsonWriter> writers = new List<JsonWriter>();
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)
{
}
}
}
此方法使所有文件保持打开状态,直到最后,以防需要附加出现在数组属性之后的“后缀”属性。请注意,有一个 limit of 16384 open files at one time ,所以如果你需要创建更多的分割文件,这将不起作用。如果在实践中从未遇到后缀属性,您可以在打开下一个文件之前关闭每个文件,并在发现任何后缀属性时抛出异常。否则,您可能需要分两次解析大文件,或者关闭并重新打开分割文件以追加它们。
以下是如何使用该方法与内存中 JSON 字符串的示例:
private static void TestSplitJson(string json, string tokenName)
{
var builders = new List<StringBuilder>();
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);
}
}
原型(prototype) fiddle .
关于c# - 拆分大型 JSON 文件的策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31410187/