using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public static partial class JsonExtensions
public static string [] SplitJsonFile(string fileName, string [] splitPath, Func<string, string, int, string, string> nameCreator)
List<string> fileNames = new List<string>();
var name = Path.GetFileNameWithoutExtension(fileName);
var ext = Path.GetExtension(fileName);
var directory = Path.GetDirectoryName(fileName);
Func<int, TextWriter> createStream = (i) =>
var newName = nameCreator(directory, name, i, ext);
var writer = new StreamWriter(newName, false, Encoding.UTF8);
using (var reader = new StreamReader(fileName, Encoding.UTF8))
JsonExtensions.SplitJson(reader,splitPath, 1, createStream, Formatting.Indented);
return fileNames.ToArray();
public static void SplitJson(TextReader textReader, IList<string> splitPath, long maxItems, Func<int, TextWriter> createStream, Formatting formatting)
if (splitPath == null || createStream == null || textReader == null)
throw new ArgumentNullException();
if (splitPath.Count < 1 || maxItems < 1)
throw new ArgumentException();
using (var reader = new JsonTextReader(textReader))
List<JsonWriter> writers = new ();
List<ParentToken> parentTokens = new ();
SplitJson(reader, splitPath, 0, maxItems, createStream, formatting, parentTokens, writers);
foreach (IDisposable writer in writers)
public ParentToken(JsonToken tokenType, IList<JToken> prefixTokens = default) => (this.TokenType, this._prefixTokens) = (tokenType, prefixTokens);
readonly IList<JToken> _prefixTokens;
public JsonToken TokenType { get; }
public IList<JToken> PrefixTokens => _prefixTokens ?? Array.Empty<JToken>();
static JsonWriter AddWriter(List<JsonWriter> writers, List<ParentToken> parentTokens, Func<int, TextWriter> createStream, Formatting formatting)
var writer = new JsonTextWriter(createStream(writers.Count)) { Formatting = formatting, AutoCompleteOnClose = false };
foreach (var parent in parentTokens)
switch (parent.TokenType)
case JsonToken.StartObject:
writer.WriteStartObject();
case JsonToken.StartArray:
writer.WriteStartArray();
throw new JsonException();
for (int i = 0; i < parent.PrefixTokens.Count; i++)
if (i == parent.PrefixTokens.Count - 1 && parent.PrefixTokens[i] is JProperty property && property.Value.Type == JTokenType.Undefined)
writer.WritePropertyName(property.Name);
parent.PrefixTokens[i].WriteTo(writer);
static (JsonWriter, int) GetCurrentWriter(List<JsonWriter> writers, List<ParentToken> parentTokens, Func<int, TextWriter> createStream, Formatting formatting)
=> writers.Count == 0 ? (AddWriter(writers, parentTokens, createStream, formatting), 0) : (writers[writers.Count-1], writers.Count-1);
static void SplitJson(JsonTextReader reader, IList<string> splitPath, int index, long maxItems, Func<int, TextWriter> createStream, Formatting formatting, List<ParentToken> parentTokens , List<JsonWriter> writers)
var startTokenType = reader.MoveToContentAndAssert().TokenType;
var startReaderDepth = reader.Depth;
var bottom = index >= splitPath.Count;
case JsonToken.StartObject:
(var firstWriter, var firstWriterIndex) = GetCurrentWriter(writers, parentTokens, createStream, formatting);
firstWriter.WriteStartObject();
var parentToken = new ParentToken(JsonToken.StartObject, new List<JToken>());
while ((doRead ? reader.ReadToContentAndAssert() : reader.MoveToContentAndAssert()).TokenType != JsonToken.EndObject)
var propertyName = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
if (propertyName == splitPath[index])
throw new JsonException(string.Format("Duplicated property name {0}", propertyName));
reader.ReadToContentAndAssert();
firstWriter.WritePropertyName(propertyName);
parentToken.PrefixTokens.Add(new JProperty(propertyName, JValue.CreateUndefined()));
parentTokens.Add(parentToken);
SplitJson(reader, splitPath, index + 1, maxItems, createStream, formatting, parentTokens, writers);
parentTokens.RemoveAt(parentTokens.Count-1);
var property = JProperty.Load(reader);
property.WriteTo(firstWriter);
parentToken.PrefixTokens.Add(property);
var property = JProperty.Load(reader);
for (int i = firstWriterIndex; i < writers.Count; i++)
property.WriteTo(writers[i]);
for (int i = firstWriterIndex; i < writers.Count; i++)
foreach (var property in parentToken.PrefixTokens)
property.WriteTo(writers[i]);
writers[i].WriteEndObject();
case JsonToken.StartArray:
var maxItemsAtDepth = bottom ? maxItems : 1L;
(var writer, var firstWriterIndex) = GetCurrentWriter(writers, parentTokens, createStream, formatting);
writer.WriteStartArray();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
if (reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None)
if (count >= maxItemsAtDepth)
writer = AddWriter(writers, parentTokens, createStream, formatting);
writer.WriteStartArray();
writer.WriteToken(reader);
parentTokens.Add(new ParentToken(JsonToken.StartArray));
SplitJson(reader, splitPath, index, maxItems, createStream, formatting, parentTokens, writers);
parentTokens.RemoveAt(parentTokens.Count-1);
for (int i = firstWriterIndex; i < writers.Count; i++)
writers[i].WriteEndArray();
(var writer, var _) = GetCurrentWriter(writers, parentTokens, createStream, formatting);
writer.WriteToken(reader);
public static partial class JsonExtensions
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
public static partial class JsonExtensions
static void DebugWriters(List<JsonWriter> writers, string step)
Debug.WriteLine($"{step}, there are {writers.Count} writers:");
for (int i = 0; i < writers.Count; i++)
var p = writers[i].GetType().GetProperty("Top", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
Debug.WriteLine($" writers[{i}].WriteState = {writers[i].WriteState}, writers[{i}].Path = {writers[i].Path}, writers[{i}].Top = {p.GetValue(writers[i])}");
public static void Test()
public static void TestNestedArray()
NestedPrefix = "nested prefix1",
NestedArray = new [] { "a11", "a12" },
NestedPostfix = "nested postfix1",
NestedPrefix = "nested prefix2",
NestedArray = new [] { "a21" },
NestedPostfix = "nested postfix2",
var json = JsonConvert.SerializeObject(obj);
var expectedJson1 = new [] { @"{""Prefix"":""prefix"",""Array"":[{""NestedPrefix"":""nested prefix1"",""NestedArray"":[""a11""],""NestedPostfix"":""nested postfix1""}],""Postfix"":""postfix""}",
@"{""Prefix"":""prefix"",""Array"":[{""NestedPrefix"":""nested prefix1"",""NestedArray"":[""a12""],""NestedPostfix"":""nested postfix1""}],""Postfix"":""postfix""}",
@"{""Prefix"":""prefix"",""Array"":[{""NestedPrefix"":""nested prefix2"",""NestedArray"":[""a21""],""NestedPostfix"":""nested postfix2""}],""Postfix"":""postfix""}" };
TestSplitJson(json, new [] { "Array", "NestedArray" }, expectedJson1);
var expectedJson2 = new [] {@"{""Prefix"":""prefix"",""Array"":[{""NestedPrefix"":""nested prefix1"",""NestedArray"":[""a11"",""a12""],""NestedPostfix"":""nested postfix1""}],""Postfix"":""postfix""}",
@"{""Prefix"":""prefix"",""Array"":[{""NestedPrefix"":""nested prefix2"",""NestedArray"":[""a21""],""NestedPostfix"":""nested postfix2""}],""Postfix"":""postfix""}"};
TestSplitJson(json, new [] { "Array" }, expectedJson2);
public static void TestQuestion31410187()
TestSplitJson(@"{""aa"" : ""b""}", "foo");
""headerName1"": ""headerVal1"",
""headerName2"": ""headerVal2"",
""element1Name1"": ""element1Value1""
""element2Name1"": ""element2Value1""
""element3Name1"": ""element3Value1""
""element4Name1"": ""element4Value1""
""element5Name1"": ""element5Value1""
""element6Name1"": ""element6Value1""
""headerNamePost"": ""headerValPost"",
""headerName1"": ""headerVal1"",
""headerName2"": ""headerVal2"",
""element1Name1"": ""element1Value1""
""element2Name1"": ""element2Value1""
""element3Name1"": ""element3Value1""
""element4Name1"": ""element4Value1""
""element5Name1"": ""element5Value1""
""element6Name1"": ""element6Value1""
TestSplitJson(json, "headerName3");
TestSplitJson(json3, "headerName3");
private static void TestSplitJson(string json, string [] splitPath, string [] expectedJson = null)
Console.WriteLine("Splitting on {0}:", string.Join(",", splitPath));
var builders = new List<StringBuilder>();
using (var reader = new StringReader(json))
JsonExtensions.SplitJson(reader, splitPath, 1, i => { builders.Add(new StringBuilder()); return new StringWriter(builders.Last()); }, Formatting.None);
Console.WriteLine("Created {0} fragments:", builders.Count);
foreach (var s in builders.Select(b => b.ToString()))
if (expectedJson != null)
Assert.AreEqual(expectedJson.Length, builders.Count);
for (int i = 0; i < builders.Count; i++)
Assert.IsTrue(JToken.DeepEquals(JToken.Parse(builders[i].ToString()), JToken.Parse(expectedJson[i])));
Console.WriteLine("Uncaught exception, incomplete split fragments:");
foreach (var sb in builders)
private static void TestSplitJson(string json, string tokenName)
var builders = new List<StringBuilder>();
using (var reader = new StringReader(json))
JsonExtensions.SplitJson(reader, new [] { tokenName }, 2, i => { builders.Add(new StringBuilder()); return new StringWriter(builders.Last()); }, Formatting.Indented);
foreach (var s in builders.Select(b => b.ToString()))
foreach (var sb in builders)
AssertCorrect(json, tokenName, builders);
private static void AssertCorrect(string json, string tokenName, List<StringBuilder> builders)
var mainToken = JObject.Parse(json);
foreach (var s in builders.Select(b => b.ToString()))
var token = JObject.Parse(s);
if (!(token.Properties().Select(p => p.Name).SequenceEqual(mainToken.Properties().Select(p => p.Name))))
throw new InvalidOperationException();
if (mainToken[tokenName] != null)
if (!(mainToken[tokenName].SequenceEqual(builders.SelectMany(b => JObject.Parse(b.ToString())[tokenName]), new JTokenEqualityComparer())))
throw new InvalidOperationException();
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];