System.Json improvements

Yesterday the WCF team released a new version of the “WCF Support for jQuery” libraries on codeplex (https://wcf.codeplex.com). Besides some bug fixes, we added some new features to make the JSON API better. Here’s a short list of them:

Events

Similarly to the XElement API, JsonValue now supports events as well, so that a user can register to be notified when a JsonValue instance is modified. Notice that JsonPrimitives are immutable, since we don’t allow the modification of the wrapped CLR object, and all types supported in JsonPrimitive are immutable themselves (numbers, boolean, char, strings, DateTime, DateTimeOffset and Uri). So the event will essentially be raised only on JsonArray and JsonObject instances.

Like the XElement type, JsonValue exposes two events: Changing and Changed. The first is raised when a change is about to be made – and that gives the user some control to prevent the change from happening, like in the example below.

  1. public class JsonObjectWithValidation
  2. {
  3.     public static void Test()
  4.     {
  5.         JsonObject jo = new JsonObject
  6.         {
  7.             { "name", "John" },
  8.             { "age", 34 }
  9.         };
  10.  
  11.         jo.Changing += new EventHandler<JsonValueChangeEventArgs>(ValidateAge);
  12.  
  13.         Console.WriteLine("Original age: {0}", jo["age"]);
  14.  
  15.         jo["age"] = 35; // valid
  16.         Console.WriteLine("New age: {0}", jo["age"]);
  17.  
  18.         try
  19.         {
  20.             Console.WriteLine("Trying to set the age to an invalid value");
  21.             jo["age"] = 355;
  22.         }
  23.         catch (InvalidOperationException e)
  24.         {
  25.             Console.WriteLine(e.Message);
  26.         }
  27.  
  28.         Console.WriteLine("New age: {0}", jo["age"]);
  29.         Console.ReadLine();
  30.     }
  31.  
  32.     static void ValidateAge(object sender, JsonValueChangeEventArgs e)
  33.     {
  34.         JsonObject jo = sender as JsonObject;
  35.         if (e.Key == "age")
  36.         {
  37.             if (e.Change == JsonValueChange.Add || e.Change == JsonValueChange.Replace)
  38.             {
  39.                 // age needs to be an integer between 0 and 150
  40.                 int newAge = e.Child.ReadAs<int>(-1);
  41.                 if (newAge < 0 || newAge > 150)
  42.                 {
  43.                     throw new InvalidOperationException("'Age' field must have a value between 0 and 150!");
  44.                 }
  45.             }
  46.         }
  47.     }
  48. }

The second event, Changed, is raised after the change is made. The main scenario for it is to bind a JsonValue instance to an UI control. Whenever changes are made, we can update the UI based on the new value. One of the samples included in the download package (Features / JsonValueEvents) shows one way it can be done.

Exposing the wrapped CLR type

A small feature that we didn’t have before, we now expose a get-only property in JsonPrimitive that returns the CLR object which was wrapped by that instance. We’ve had converters (casts, ReadAs and TryReadAs) before so that you could ask for a CLR object from the JsonPrimitive, but there are cases where you don’t actually know the type – for example, if you want to clone the JSON DOM. This can now be done with a simple extension method, like the one shown below.

  1. public static class JsonValueCloneExtension
  2. {
  3.     public static JsonValue Clone(this JsonValue value)
  4.     {
  5.         if (value == null)
  6.         {
  7.             return null;
  8.         }
  9.  
  10.         switch (value.JsonType)
  11.         {
  12.             case JsonType.Default:
  13.                 return value; // only 1 Default instance
  14.             case JsonType.Boolean:
  15.             case JsonType.Number:
  16.             case JsonType.String:
  17.                 // It's a JsonPrimitive
  18.                 JsonPrimitive jp = (JsonPrimitive)value;
  19.                 JsonPrimitive newPrimitive;
  20.                 JsonPrimitive.TryCreate(jp.Value, out newPrimitive); // we know it will succeed, since the value came from a JsonPrimitive itself
  21.                 return newPrimitive;
  22.             case JsonType.Array:
  23.                 JsonArray newArray = new JsonArray();
  24.                 JsonArray ja = (JsonArray)value;
  25.                 foreach (JsonValue jv in ja)
  26.                 {
  27.                     newArray.Add(Clone(jv));
  28.                 }
  29.  
  30.                 return newArray;
  31.             default:
  32.                 // It's a JsonObject
  33.                 JsonObject newObject = new JsonObject();
  34.                 JsonObject jo = (JsonObject)value;
  35.                 foreach (KeyValuePair<string, JsonValue> item in jo)
  36.                 {
  37.                     newObject.Add(item.Key, Clone(item.Value));
  38.                 }
  39.  
  40.                 return newObject;
  41.         }
  42.     }
  43. }

Operations and conversions in dynamic notation

In an effort to make Javascript users more at ease in dealing with JsonValue instances, we’ve improved the dynamic support in JsonValue to make certain operations more natural in the dynamic world. Among those operations:

  • Implicit conversions to primitive types (no need to cast)
  • Operators support – a dynamic reference to a JsonPrimitive can now be used in addition, subtraction, relational operations, etc.

The code below shows some examples of that. The sample included in the download package (Features / JsonValueDynamic) also shows more instances of that.

  1. public class JsonValueAndDynamics
  2. {
  3.     public static void Test()
  4.     {
  5.         dynamic ja = new JsonArray
  6.         {
  7.             new JsonObject
  8.             {
  9.                 { "Name", "John" },
  10.                 { "DOB", new DateTime(1984, 01, 17) },
  11.                 { "Salary", 90000.00 },
  12.                 { "IsMarried", false },
  13.             },
  14.             new JsonObject
  15.             {
  16.                 { "Name", "Joe" },
  17.                 { "DOB", new DateTime(1983, 02, 18) },
  18.                 { "Salary", 105000.00 },
  19.                 { "IsMarried", true },
  20.             },
  21.         };
  22.  
  23.         for (int i = 0; i < ja.Count; i++)
  24.         {
  25.             string name = ja[i].Name;
  26.             if (ja[i].IsMarried)
  27.             {
  28.                 Console.WriteLine(name + " is married");
  29.             }
  30.             else
  31.             {
  32.                 Console.WriteLine(name + " is not married");
  33.             }
  34.  
  35.             Console.WriteLine(name + " earns " + (ja[i].Salary > 100000 ? "more" : "less") + " than 100k per year");
  36.         }
  37.  
  38.         Console.ReadLine();
  39.     }
  40. }

What next?

Download the new release (at https://wcf.codeplex.com/releases/view/57702) and tell us what you think. We’ll look for addressing any issues that are raised in our next release.