using System.Collections.Generic;
using Newtonsoft.Json.Linq;
public static void Main()
string inputDocument = @"
{ ""Item_1"": ""Desc_3A1"" },
{ ""Item_2"": ""Desc_3A2"" },
{ ""Item_3"": ""Desc_3A3"" }
{ ""Item_3B1"": ""Desc_3B1"" },
{ ""Item_1"": ""Desc_3B11"" },
{ ""Item_2"": ""Desc_3B12"" },
{ ""Item_3"": ""Desc_3B13"" }
{ ""Item_1"": ""Desc_3B21"" },
{ ""Item_2"": ""Desc_3B22"" },
{ ""Item_3"": ""Desc_3B23"" }
string expectedFindItem_1 = @"
{ ""Item_1"": ""Desc_3A1"" }
{ ""Item_1"": ""Desc_3B11"" }
{ ""Item_1"": ""Desc_3B21"" }
var root = (JContainer)JToken.Parse(inputDocument);
var expected = (JContainer)JToken.Parse(expectedFindItem_1);
var t1merged = MergeAncestry(root.SelectTokens("$.Array3A"));
Console.WriteLine($"{t1merged.ToString()}");
Console.WriteLine("------------------------");
var t2merged = MergeAncestry(root.SelectTokens("$.Array3B.[*].*.[*].Item_1"));
Console.WriteLine($"{t2merged.ToString()}");
Console.WriteLine("------------------------");
var t3merged = MergeAncestry(root.SelectTokens("$.Array3B.[*].Array3B1.[*].*"));
Console.WriteLine($"{t3merged.ToString()}");
Console.WriteLine("------------------------");
var t4merged = MergeAncestry(root.SelectTokens("$.DoesntExist"));
Console.WriteLine($"{t4merged.ToString()}");
Console.WriteLine("------------------------");
var t5merged = MergeAncestry(root.SelectTokens("$..Item_1"));
Console.WriteLine($"{t5merged.ToString()}");
Console.WriteLine(JToken.DeepEquals(expected, t5merged));
static JToken MergeAncestry(IEnumerable<JToken> tokens)
if (tokens == null || !tokens.Any())
var tokensByDepth = tokens
.Distinct(ObjectReferenceEqualityComparer<JToken>.Default)
.GroupBy(t => t.Ancestors().Count())
g => g.Select(node => new CarbonCopyToken { Original = node, CarbonCopy = node.DeepClone() })
int depth = tokensByDepth.Keys.Max();
for (int i = depth; i > 0; i--)
if (!tokensByDepth.ContainsKey(i - 1))
tokensByDepth.Add(i - 1, new List<CarbonCopyToken>());
foreach (var parent in MergeCommonParents(tokensByDepth[i]))
tokensByDepth[i - 1].Add(parent);
var cc = tokensByDepth[0].FirstOrDefault();
return cc?.CarbonCopy ?? new JObject();
static IEnumerable<CarbonCopyToken> MergeCommonParents(IEnumerable<CarbonCopyToken> tokens)
var newParents = tokens.GroupBy(t => t.Original.Parent).Select(g => new CarbonCopyToken {
Original = g.First().Original.Parent,
CarbonCopy = CopyCommonParent(g.First().Original.Parent, g.AsEnumerable())
static JToken CopyCommonParent(JToken parent, IEnumerable<CarbonCopyToken> children)
return new JProperty(((JProperty)parent).Name, children.First().CarbonCopy);
var newParentArray = new JArray();
foreach (var child in children)
newParentArray.Add(child.CarbonCopy);
var newParentObject = new JObject();
foreach (var child in children)
newParentObject.Add(child.CarbonCopy);
public JToken Original { get; set; }
public JToken CarbonCopy { get; set; }
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
private static readonly IEqualityComparer<T> _defaultComparer;
static ObjectReferenceEqualityComparer() { _defaultComparer = new ObjectReferenceEqualityComparer<T>(); }
public static IEqualityComparer<T> Default { get { return _defaultComparer; } }
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
return ReferenceEquals(x, y);
public int GetHashCode(T obj)
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);