Serializing Objects as JavaScript using Atlas, JSON.NET and AjaxPro
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.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
I corrected the bug you pointed out on my blog and here. I also added some shortcut methods to the JavaScriptConvert class for deserializing. I have mainly been using the library myself for serializing objects so there were a few bugs left. The 1.0.1 version is now on the downloads page - http://www.newtonsoft.com/downloads/#json
Ajax.NET Professional will use different JSON representations for communication direction. What I didn't implement is the "new" operator Atlas is using. Atlas has only implemented the "new" operator for the Date object, or is it possible to use own created classes (from JavaScript code)? No, so you are comparing apple with ajax... ;)
The __type property is used to use any inherited class as argument, too, which is not working in Atlas!
See my post here:
http://weblogs.asp.net/mschwarz/archive/2006/07/05/Serializing-Objects-as-JavaScript-using-Atlas-and-AjaxPro.aspx
Regards,
Michael
Are the other options worth considering? I personally would not wanna write my own version or copy. Also, I'm not sure if there would be any IP issues if we were to copy. Does anyone know?
Can it be the silver bullet?
The question about CAS must be kept in perspective. When you are writing a generic library, then you need to take steps to minimize your runtime requirements. The minute you say that you have to run in High or Full trust, you immediately remove a large segment of potential users. I am seeing more and more hosting companies move to running Medium Trust environments based on the recommendations of Microsoft.
So given that we are discussing a generic web library I think you should exclude reflection as an option (assuming the rest of the library will run in a partial trust environment). I also get a sour taste from the thought of copying/modifying someone else's intellectual property (although it might be ok if they found the comparable MONO code and used that as a basis - depending on the license compatability - and given the correct code attributions.)
class Object
def to_json
# implementation
end
end
This makes a private patch to Object at runtime and is how Rails injects a new method into Object.
Obviously you need to understand what the code is doing, and since you should understand such the "write your own" option seems simply repetitive.
With AjaxPro I'd like to develop on the client side JavaScript with the power of .NET. So, I'm currently working on a newer release that will not use the "new" statement because of security requests.
Regards,
Michael
Non-C# programmer types (who don’t know that the “M” means “decimal type”) might be tempted to mug you at the next conference they see you at.
Decompiling and then redistributing the code is against the license. Sorry, I work for Microsoft, I have to say that. :)
Calling via private reflection is not only a CAS issue, it's also potentially a performance issue. Private reflection can be on the order of two magnitudes slower than public reflection. See http://hyperthink.net/blog/CommentView,guid,14736081-2589-474a-b867-85fa1c33f4d7.aspx for more info.
So, what are you left with? Write on yourself, and open source it. :)
Brad - I doubt that making a single Reflection call per serialization/deserialization will have a significant performance impact on an ASP.NET Web application which internally must be using many, many Reflection calls. In looping situations - sure, but one of calls in a non-chatty interface like this - unlikely.
I think I must be missing something in your conversation with Michael. I think you are saying you want to round trip an object server-to-client-to-server and get the same thing back. Michael seems to think you want to round tip an object server1-to-client-to-server2 where server1 and server2 are running two different libraries. Did I misunderstand?
1) new Date(...);
2) {"Year":2006,"Month":4,"Day":20,...}
What you can do is running an ASP.NET web site that will get the JSON string from web server (serialize a .NET object) and use this object as a new argument for another AjaxMethod (deserialize). This is working great if you have a look at my example page:
http://munich.schwarz-interactive.de/datatype.aspx
a == b == a
The round-tripping of serialized types is working when round-tripping is including the client-side JavaScript code. ;)
>> say object.to_json and get JSON strings.
Between reflection and C# 3.0's extenstion methods you should be able to do this in C#:
public static class JsonExtension {
public static string ToJavaScriptObjectNotation(
this object obj) {
// ... Create StringBuilder,
// ... reflect on object,
// ... return strBld.ToString();
}
}
Customer customer = new Customer { Name="Scott", ... }
customer.ToJavaScriptObjectNotation();
http://keithhill.spaces.msn.com/blog/cns!5A8D2641E0963A97!515.entry
I did some more testing with some different data types, see post at http://weblogs.asp.net/mschwarz/archive/2006/07/10/Serializing-Objects-as-JavaScript-using-Atlas-and-AjaxPro-_5B00_Part-2_5D00_.aspx.
Anyhow, I've released a new version of Json.NET with my own escape string method that improves on what was there, both in readability and performance. I had always been afraid to do my own after seeing how complicated the Microsoft version was but there was surprisingly little to it.
Comments are closed.
In any case, reflection could mean a performance hit, but that would be my choice.
Sachin