/// <summary> /// Formats and prints the resulting <see cref="ChangeList{T}"/> from a <see cref="DiffTrees(JsonNode, JsonNode)"/>. /// </summary> /// <returns> /// A string representation of the change list. /// </returns> public static string PrintTreeChangeList(ChangeList <JsonNode> changeList) { var treeToPrint = new PrintNode(); for (int i = 0; i < changeList.Count; ++i) { var change = changeList[i]; var node = change.Value; var path = new LinkedList <string>(); // Use "it.Parent != null" instead of "it != null" to // ignore printing the root parent node that matches the opening bracket // for all JSON objects for (var it = node; it.Parent != null; it = it.Parent) { path.AddFirst(it.Name); } var printNode = treeToPrint; // Build a tree of the change list values, placing nodes based off // their positions in the old or new tree foreach (var pathAtom in path) { if (!printNode.Children.ContainsKey(pathAtom)) { printNode.Children.Add(pathAtom, new PrintNode()); } printNode = printNode.Children[pathAtom]; } printNode.ChangedNodes.Add(change); } return(treeToPrint.ToString()); }
/// <summary> /// Diffs two trees. /// </summary> /// <param name="rootA"> /// The root of the original tree. /// </param> /// <param name="rootB"> /// The root of the transformed tree. /// </param> /// <returns> /// A <see cref="ChangeList{JsonNode}"/> containing /// the leaf nodes that differ. /// </returns> public static ChangeList <JsonNode> DiffTrees(JsonNode rootA, JsonNode rootB) { var leavesA = CollectValueNodes(rootA); var leavesB = CollectValueNodes(rootB); var changeList = new ChangeList <JsonNode>(leavesA, leavesB); return(changeList); }
/// <summary> /// Returns the tree of changes encapusulated by this <see cref="PrintNode"/> as an indented, formatted string. /// </summary> public override string ToString() { using (var sbPool = Pools.GetStringBuilder()) { var sb = sbPool.Instance; var stack = new Stack <RecursionState>(); stack.Push(new RecursionState(this, -1, null)); while (stack.Count != 0) { var curr = stack.Pop(); var indentPrefix = curr.Level > 0 ? new string('\t', curr.Level) : string.Empty; if (curr.Name != null) { sb.AppendLine(indentPrefix + curr.Name); } // Each PrintNode represents one position in a tree. The values of a PrintNode represent nodes that were added or removed at that position. // The max number of values at a position is 2, when a node is removed from one position and a new one is added at the same position. // This is equivalent to modifying the state of that node. switch (curr.PrintNode.ChangedNodes.Count) { // A node exists in one tree that does not exist in the other // In this case, print all the values of the node as either added or removed case 1: var changeListValue = curr.PrintNode.ChangedNodes[0]; sb.Append(indentPrefix + changeListValue.ToString()); break; // A node exists in both trees, but with different values (equivalent to modifying the node) // Consolidate the print out by diffing just the values case 2: ChangeList <JsonNode> .ChangeListValue removed, added; if (curr.PrintNode.ChangedNodes[0].ChangeType == ChangeList <JsonNode> .ChangeType.Removed) { removed = curr.PrintNode.ChangedNodes[0]; added = curr.PrintNode.ChangedNodes[1]; } else { removed = curr.PrintNode.ChangedNodes[1]; added = curr.PrintNode.ChangedNodes[0]; } // Order of removed vs added node is not guaranteed in the change list, // but when diffing the nodes' values, the removed node represents a node from the old tree, // so it should go first and represent the old list to get the correct diff. var changeList = new ChangeList <string>(removed.Value.Values, added.Value.Values); if (changeList.Count == 0) { // There was no difference in values between the removed node and added node, // this means that the node simply moved positions. // In this case, print the full list of values for both the removed and added node sb.Append(indentPrefix + removed.ToString()); sb.Append(indentPrefix + added.ToString()); } else { // Otherwise, rely on the normal diff sb.Append(changeList.ToString(indentPrefix)); } break; default: break; } foreach (var child in curr.PrintNode.Children) { stack.Push(new RecursionState(child.Value, curr.Level + 1, child.Key)); } } return(sb.ToString()); } }