Ajax is shiny. In our talk at TechEd, Patrick and I mentioned that our next plan was a dynamic endpoint for our financial services that spoke JSON to complement our "Dirty SOAP" endpoint. This would make auto-complete dropdowns and sortable grids REALLY easy when interfacing with our SDK that already supports a large message set for banking-type things like GetPayees, GetAccountHistory.
The first step to make this happen will be JSON serialization round-tripping. For example, I'd like to take this object (instance)...
public class Person
{
public string firstName = "Scott";
public string lastName = "Hanselman";
public DateTime birthDay = new DateTime(1970, 1, 15, 1, 1, 0);
public decimal moneyInPocket = 4.5M;
}
...and serialize it to JSON thusly:
{"firstName":"Scott", "lastName":"Hanselman", "birthDay": new Date(1213260000), "moneyInPocket":4.5}
I was already planning to create a JavaScript serializer as Corillian already has fixed-length, delimited, name-value pair and other serializers for any object.
I took a look at JSON.NET thinking it'd be a nice, lightweight serializer, and while it's cool on initial first glance, this release didn't pass the "fall into the pit of success" test for an object serializer.
UPDATE: Json.NET has been updated and now works as expected and includes helper methods to make the job simpler...new code below.
1 using System;
2 using System.IO;
3 using System.Collections.Generic;
4 using System.Text;
5
6 namespace ConsoleApplication1
7 {
8 public class Person
9 {
10 public string firstName = "Scott";
11 public string lastName = "Hanselman";
12 public DateTime birthDay = new DateTime(1970, 1, 15, 1, 1, 0);
13 public decimal moneyInPocket = 4.5M;
14 }
15
16 class Program
17 {
18 static void Main(string[] args)
19 {
20 Person p = new Person();
21 string output = Newtonsoft.Json.JavaScriptConvert.SerializeObject(p);
22
23 output = output.Replace("Scott", "Fred");
24 output = output.Replace("Hanselman", "Jones");
25
26 Person anotherP = Newtonsoft.Json.JavaScriptConvert.DeserializeObject(output, typeof(Person)) as Person;
27 Console.WriteLine(anotherP.firstName + " " + anotherP.lastName);
28 }
29 }
30 }
31
Then I tried Ajax.NET 6.7.2.1 from Michael Schwarz...
1 using System;
2 using System.IO;
3 using System.Collections.Generic;
4 using System.Text;
5 using Microsoft.Web.Script.Serialization;
6
7 namespace ConsoleApplication1
8 {
9 public class Person
10 {
11 public string firstName = "Scott";
12 public string lastName = "Hanselman";
13 public DateTime birthDay = new DateTime(1970, 1, 15, 1, 1, 0);
14 public decimal moneyInPocket = 4.5M;
15 }
16
17 class Program
18 {
19 static void Main(string[] args)
20 {
21 Person p = new Person();
22 string output = AjaxPro.JavaScriptSerializer.Serialize(p);
23
24 output = output.Replace("Scott", "Fred");
25 output = output.Replace("Hanselman", "Jones");
26
27 Person anotherP = AjaxPro.JavaScriptDeserializer.DeserializeFromJson(output, typeof(Person)) as Person;
28 Console.WriteLine(anotherP.firstName + " " + anotherP.lastName);
29 }
30 }
31 }
32
...but got this exception on the deserialization. When reflectoring through the source, this implies that null was passed to DateTimeConverter.Deserialize. I think an ArgumentNullException would have been clearer.
System.NotSupportedException was unhandled
Message="Specified method is not supported."
Source="AjaxPro.2"
StackTrace:
at AjaxPro.DateTimeConverter.Deserialize(IJavaScriptObject o, Type t)
at AjaxPro.JavaScriptDeserializer.Deserialize(IJavaScriptObject o, Type type)
at AjaxPro.JavaScriptDeserializer.DeserializeCustomObject(JavaScriptObject o, Type type)
at AjaxPro.JavaScriptDeserializer.Deserialize(IJavaScriptObject o, Type type)
at AjaxPro.JavaScriptDeserializer.DeserializeFromJson(String json, Type type)
at ConsoleApplication1.Program.Main(String[] args) in C:\Documents and Settings\Scott\Desktop\ConsoleApplication1\ConsoleApplication1\Program.cs:line 29
However, I was able to get it to round-trip when I removed the DateTime. Not sure what's up with that.
Also interesting, Ajax.NET (AjaxPro) saved the Type/Assembly Qualified Name in the resulting JSON. I can see why they'd want to do that, but one of the nice things about JavaScript and JSON in general is the cleanliness and flexibility of the wire format. This could complicate things if I've got different CLR types on the server consuming the same serialized JSON from the client. It also serializes the DateTime in a different way than I'm used to.
{"__type":"ConsoleApplication1.Person, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "firstName":"Scott", "lastName":"Hanselman", "birthDay": new Date(Date.UTC(1970,0,15,9,1,0,0)), "moneyInPocket":4.5}
Moving to Microsoft's Atlas, similar and slightly simpler code works just fine like this:
1 using System;
2 using System.IO;
3 using System.Collections.Generic;
4 using System.Text;
5 using Microsoft.Web.Script.Serialization;
6
7 namespace ConsoleApplication1
8 {
9 public class Person
10 {
11 public string firstName = "Scott";
12 public string lastName = "Hanselman";
13 public DateTime birthDay = new DateTime(1970, 1, 15, 1, 1, 0);
14 public decimal moneyInPocket = 4.5M;
15 }
16
17 class Program
18 {
19 static void Main(string[] args)
20 {
21 Person p = new Person();
22 string output = JavaScriptObjectSerializer.Serialize(p);
23
24 output = output.Replace("Scott", "Fred");
25 output = output.Replace("Hanselman", "Jones");
26
27 Person anotherP = JavaScriptObjectDeserializer.Deserialize(output, typeof(Person)) as Person;
28 Console.WriteLine(anotherP.firstName + " " + anotherP.lastName);
29 }
30 }
31 }
32
...giving the expected output of
{"firstName":"Scott", "lastName":"Hanselman", "birthDay": new Date(1213260000), "moneyInPocket":4.5}
Between these three JSON serializers and this simple test, only ATLAS "just worked." (Assuming my 'simple test' isn't flawed.) For now I'll use these Atlas assemblies for my JSON serialization needs, but it'd be nice if I could back-port the chunky parts of one of the other libraries to .NET 1.1 my for projects that can't use 2.0.
UPDATE: Json.NET and Atlas appear to work the same with this simple test.
Of course, the really sad thing is that John Lam or my boss will step in here any minute and remind us that in Ruby on Rails you can just say object.to_json and get JSON strings. Phooey!
As a totally unrelated aside, and for the purposes of starting a discussion - in the JSON.NET source code the author James Newton-King appears to have decompiled the source for System.Web.UI.Util.QuoteJScriptString from ASP.NET and included the decompiled source (with a few modifications) directly in JSON.NET's JavaScriptUtils.cs which is then licensed under the Creative Commons Attribution 2.5 License.
UPDATE: James Newton King updated the JSON Framework and removed the CC license from the JavaScriptUtils.cs file as it wasn't his intent to release Microsoft code like that. He also included an explanatory comment. Seems like a reasonable solution to me.
The question is: When a useful function exists in the .NET Framework, but is marked internal/private, for whatever reason. Is it better to:
A. decompile it and include it in your code anyway (hopefully with an explanatory comment).
B. write your own from scratch.
C. use the internal/private/whatever method via Reflection.
Discuss.
Hosting By