Deep event propagation in JsonValue

One of the features added in the latest release of the WCF for jQuery support in Codeplex was the ability for a user to register to listen to modifications in a JsonValue instance. However, if you have a JSON DOM and you want to be notified for changes in any member of the graph, you’d need to register for changes in all elements in the graph. This sample provides a wrapper which can be used for such. It’s a simple wrapper which essentially adds a new field to the JsonValue object, its parent in the graph. So when any change is made deep in the graph, the wrapper can propagate the change to its parent until it reaches the root.

JsonValueWrapper class

  1. /// <summary>
  2. /// Wrapper for a <see cref="JsonValue"/> instance, which can raise events for changes in the whole JSON
  3. /// graph (not only in the immediate children).
  4. /// </summary>
  5. public class JsonValueWrapper : IDisposable
  6. {
  7.     private JsonValueWrapper parent;
  8.     private object parentKey;
  9.     private JsonValue wrappedValue;
  10.     private List<JsonValueWrapper> children;
  11.     private bool disposed = false;
  12.  
  13.     /// <summary>
  14.     /// Creates a new instance of the <see cref="JsonValueWrapper"/> class.
  15.     /// </summary>
  16.     /// <param name="parent">The parent of the <paramref name="value"/> instance in the JSON graph, or <code>null</code> if
  17.     /// the <paramref name="value"/> is the root of the graph.</param>
  18.     /// <param name="parentKey">The key for the <paramref name="value"/> for which it can be accessed by the parent, or <code>null</code>
  19.     /// if the <paramref name="value"/> is the root of the graph.</param>
  20.     /// <param name="value">The <see cref="JsonValue"/> instance being wrapped.</param>
  21.     public JsonValueWrapper(JsonValueWrapper parent, object parentKey, JsonValue value)
  22.     {
  23.         if (value == null)
  24.         {
  25.             throw new ArgumentNullException("value", "The wrapped JsonValue cannot be null.");
  26.         }
  27.  
  28.         if (parentKey == null)
  29.         {
  30.             if (parent != null)
  31.             {
  32.                 throw new ArgumentException("If parent is not null, parentKey must also be not null.");
  33.             }
  34.         }
  35.         else
  36.         {
  37.             if (parent == null)
  38.             {
  39.                 throw new ArgumentException("If parent is null, parentKey must also be null.");
  40.             }
  41.  
  42.             switch (Type.GetTypeCode(parentKey.GetType()))
  43.             {
  44.                 case TypeCode.Int32:
  45.                     if (parent.Value.JsonType != JsonType.Array)
  46.                     {
  47.                         throw new ArgumentException("Int32 parentKey values can only be used with a JsonArray parent.");
  48.                     }
  49.  
  50.                     if (!object.ReferenceEquals(value, parent.Value[(int)parentKey]))
  51.                     {
  52.                         throw new ArgumentException("The wrapped value does not correspond to the item indexed by the parentKey in the parent.");
  53.                     }
  54.  
  55.                     break;
  56.                 case TypeCode.String:
  57.                     if (parent.Value.JsonType != JsonType.Object)
  58.                     {
  59.                         throw new ArgumentException("String parentKey values can only be used with a JsonObject parent.");
  60.                     }
  61.  
  62.                     if (!object.ReferenceEquals(value, parent.Value[(string)parentKey]))
  63.                     {
  64.                         throw new ArgumentException("The wrapped value does not correspond to the item indexed by the parentKey in the parent.");
  65.                     }
  66.  
  67.                     break;
  68.                 default:
  69.                     throw new ArgumentException("The parentKey value must be either an Int32 or a String.");
  70.             }
  71.         }
  72.  
  73.         this.parent = parent;
  74.         this.parentKey = parentKey;
  75.         this.wrappedValue = value;
  76.         this.children = new List<JsonValueWrapper>();
  77.         this.Initialize();
  78.         this.wrappedValue.Changed += new EventHandler<JsonValueChangeEventArgs>(WrappedValueChanged);
  79.     }
  80.  
  81.     /// <summary>
  82.     /// Raised when the wrapped <see cref="JsonValue"/> instance, or any of its children (recursively) has changed.
  83.     /// </summary>
  84.     public event EventHandler<DeepJsonValueEventArgs> DeepChanged;
  85.  
  86.     /// <summary>
  87.     /// Gets the wrapper for the parent of the wrapped <see cref="JsonValue"/> instance in the JSON graph. If the wrapped
  88.     /// instance is the root of the graph, it will return <code>null</code>.
  89.     /// </summary>
  90.     public JsonValueWrapper Parent
  91.     {
  92.         get { return this.parent; }
  93.     }
  94.  
  95.     /// <summary>
  96.     /// Gets the key for the wrapped <see cref="JsonValue"/> instance where it can be accessed by its parent in the JSON graph. If the wrapped
  97.     /// instance is the root of the graph, it will return <code>null</code>.
  98.     /// </summary>
  99.     public object ParentKey
  100.     {
  101.         get { return this.parentKey; }
  102.     }
  103.  
  104.     /// <summary>
  105.     /// Gets the <see cref="JsonValue"/> instance wrapped by this object.
  106.     /// </summary>
  107.     public JsonValue Value
  108.     {
  109.         get { return this.wrappedValue; }
  110.     }
  111.  
  112.     /// <summary>
  113.     /// Wraps a <see cref="JsonValue"/> instance in a <see cref="JsonValueWrapper"/> instance.
  114.     /// </summary>
  115.     /// <param name="parent">The parent of the <paramref name="value"/> instance in the JSON graph, or <code>null</code> if
  116.     /// the <paramref name="value"/> is the root of the graph.</param>
  117.     /// <param name="parentKey">The key for the <paramref name="value"/> for which it can be accessed by the parent, or <code>null</code>
  118.     /// if the <paramref name="value"/> is the root of the graph.</param>
  119.     /// <param name="value">The instance to be wrapped.</param>
  120.     /// <returns>An instance of the <see cref="JsonValueWrapper"/> class which wraps the given <see cref="JsonValue"/> instance.</returns>
  121.     public static JsonValueWrapper WrapJsonValue(JsonValueWrapper parent, object parentKey, JsonValue value)
  122.     {
  123.         if (value == null)
  124.         {
  125.             return null;
  126.         }
  127.  
  128.         return new JsonValueWrapper(parent, parentKey, value);
  129.     }
  130.  
  131.     /// <summary>
  132.     /// Disposes this instance, removing the event handlers for the wrapped value (and all of its descendants).
  133.     /// </summary>
  134.     public void Dispose()
  135.     {
  136.         this.Dispose(true);
  137.         GC.SuppressFinalize(this);
  138.     }
  139.  
  140.     protected virtual void Dispose(bool disposing)
  141.     {
  142.         if (!this.disposed)
  143.         {
  144.             if (disposing)
  145.             {
  146.                 this.wrappedValue.Changed -= new EventHandler<JsonValueChangeEventArgs>(this.WrappedValueChanged);
  147.                 foreach (var child in this.children)
  148.                 {
  149.                     child.Dispose();
  150.                 }
  151.             }
  152.  
  153.             this.disposed = true;
  154.         }
  155.     }
  156.  
  157.     ~JsonValueWrapper()
  158.     {
  159.         this.Dispose(false);
  160.     }
  161.  
  162.     private void Initialize()
  163.     {
  164.         if (this.wrappedValue != null)
  165.         {
  166.             if (this.wrappedValue.JsonType == JsonType.Array)
  167.             {
  168.                 for (int i = 0; i < this.wrappedValue.Count; i++)
  169.                 {
  170.                     JsonValueWrapper wrappedChild = JsonValueWrapper.WrapJsonValue(this, i, this.wrappedValue[i]);
  171.                     if (wrappedChild != null)
  172.                     {
  173.                         this.children.Add(wrappedChild);
  174.                     }
  175.                 }
  176.             }
  177.             else if (this.wrappedValue.JsonType == JsonType.Object)
  178.             {
  179.                 foreach (var child in this.wrappedValue)
  180.                 {
  181.                     JsonValueWrapper wrappedChild = JsonValueWrapper.WrapJsonValue(this, child.Key, child.Value);
  182.                     if (wrappedChild != null)
  183.                     {
  184.                         this.children.Add(wrappedChild);
  185.                     }
  186.                 }
  187.             }
  188.         }
  189.     }
  190.  
  191.     private void WrappedValueChanged(object sender, JsonValueChangeEventArgs e)
  192.     {
  193.         this.FireDeepChanged(e, sender as JsonValue, null);
  194.     }
  195.  
  196.     private void FireDeepChanged(JsonValueChangeEventArgs originalEvent, JsonValue originalSender, params object[] keys)
  197.     {
  198.         if (this.DeepChanged != null)
  199.         {
  200.             this.DeepChanged(this, new DeepJsonValueEventArgs(originalEvent, originalSender, keys));
  201.         }
  202.  
  203.         if (this.Parent != null)
  204.         {
  205.             List<object> eventKeys = new List<object>();
  206.             if (this.parentKey != null)
  207.             {
  208.                 eventKeys.Add(this.parentKey);
  209.             }
  210.  
  211.             if (keys != null)
  212.             {
  213.                 eventKeys.AddRange(keys);
  214.             }
  215.  
  216.             this.Parent.FireDeepChanged(originalEvent, originalSender, eventKeys.ToArray());
  217.         }
  218.     }
  219. }

DeepJsonValueEventArgs class

  1. /// <summary>
  2. /// Provide data for the <see cref="JsonValueWrapper.DeepChanged"/> event.
  3. /// </summary>
  4. public class DeepJsonValueEventArgs : EventArgs
  5. {
  6.     private JsonValueChangeEventArgs change;
  7.     private JsonValue originalSender;
  8.     private List<object> keys;
  9.  
  10.     /// <summary>
  11.     /// Creates a new instance of the <see cref="DeepJsonValueEventArgs"/> class.
  12.     /// </summary>
  13.     /// <param name="originalChange">The data for the original change event which triggered the <see cref="JsonValueWrapper.DeepChanged"/> event.</param>
  14.     /// <param name="originalSender">The sender for the original change event which triggered the <see cref="JsonValueWrapper.DeepChanged"/> event.</param>
  15.     /// <param name="keys">The sequence of the keys to the change where the original event occurred.</param>
  16.     public DeepJsonValueEventArgs(JsonValueChangeEventArgs originalChange, JsonValue originalSender, IEnumerable<object> keys)
  17.     {
  18.         this.change = originalChange;
  19.         this.originalSender = originalSender;
  20.         if (keys == null)
  21.         {
  22.             this.keys = new List<object>();
  23.         }
  24.         else
  25.         {
  26.             this.keys = new List<object>(keys);
  27.         }
  28.     }
  29.  
  30.     /// <summary>
  31.     /// Gets the data for the original change event.
  32.     /// </summary>
  33.     public JsonValueChangeEventArgs OriginalChange
  34.     {
  35.         get
  36.         {
  37.             return this.change;
  38.         }
  39.     }
  40.  
  41.     /// <summary>
  42.     /// Gets the sender for the original change event.
  43.     /// </summary>
  44.     public JsonValue OriginalSender
  45.     {
  46.         get
  47.         {
  48.             return this.originalSender;
  49.         }
  50.     }
  51.  
  52.     /// <summary>
  53.     /// Gets the sequence of the keys from the event source to the item where the event occurred.
  54.     /// </summary>
  55.     public IEnumerable<object> Keys
  56.     {
  57.         get
  58.         {
  59.             return this.keys.AsReadOnly();
  60.         }
  61.     }
  62. }

Coming up

A WPF control which can be used to visualize JsonValue graphs.