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.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public static partial class JsonExtensions
public static void FixElementArrays(this JToken root)
var regex = new Regex("^(.+)\\[[0-9]+\\]$");
if (root is JContainer container)
from o in container.DescendantsAndSelf().OfType<JObject>()
let matches = o.Properties()
.Select(p => (Property : p, Match : regex.Match(p.Name)))
.Where(m => m.Match.Success)
.Select(m => (m.Property, Name : m.Match.Groups[1].Value))
let groups = matches.GroupBy(m => m.Name)
select (Object : o, Name : g.Key, Values : g.Select(m => m.Property.Value).ToList());
foreach (var g in query.ToList())
IList<JToken> objAsList = g.Object;
var insertIndex = objAsList.IndexOf(g.Values[0].Parent);
g.Values.ForEach(v => v.RemoveFromLowestPossibleParent());
objAsList.Insert(insertIndex, new JProperty(g.Name, new JArray(g.Values)));
public static JToken RemoveFromLowestPossibleParent(this JToken node)
var property = node.Parent as JProperty;
var contained = property ?? node;
if (contained.Parent != null)
public static void Test()
new List<int>(new [] { 0, 1 } ).Insert(2, 0);
foreach (var pair in GetJson())
var jsonString = pair.Original;
var rootToken = JToken.Parse(jsonString);
rootToken.FixElementArrays();
var fixedJsonString = rootToken.ToString();
Console.WriteLine("\nFixed JSON:");
Console.WriteLine(rootToken);
if (pair.Required != null)
Assert.IsTrue(JToken.DeepEquals(rootToken, JToken.Parse(pair.Required)));
static IEnumerable<(string Original, string Required)> GetJson()
""elements[0]"": ""Value 0"",
""elements[1]"": ""Value 1"",
""anotherelement[0]"": ""Another value 0"",
""anotherelement[1]"": ""Another value 1"",
""elements[0]"": ""Value 0"",
""elements[1]"": ""Value 1"",
""anotherelement[0]"": ""Another value 0"",
""anotherelement[1]"": ""Another value 1""
""text"":""3 hours 54 mins"",
""text"":""1 hour 44 mins"",
""text"":""1 day 18 hours"",
""text"": ""3 hours 54 mins"",
""text"": ""1 hour 44 mins"",
""text"": ""1 day 18 hours"",
""text"": ""3 hours 54 mins"",
""elements[0]"": ""Element 0 value"",
""elements[1]"": ""Element 1 value"",
""anotherelement[0]"": ""Another value 0"",
""anotherelement[1]"": ""Another value 1""
""text"": ""1 hour 44 mins"",
""status"": ""Tlon Ukbar""
""text"": ""1 day 18 hours"",
""status"": [""Tlon Ukbar""]
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.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];