However, after mixing JScript with VBScript (the other day), I got thinking, had a little play around, and realised that wee bit of server-side JS can make JSON production super easy:
<%@ LANGUAGE=VBScript LCID=5129 %>
<%
Dim sJSON, package, book, author
Set package = JsInterop.Object("VbInterop.Json.RpcResponse")
Set package.result = JsInterop.Object("VbInterop.Json.BooksCollection")
Set package.result.books = JsInterop.NewArray()
For i = 1 to 3
Set book = JsInterop.Object("VbInterop.Json.Book")
book.title = "Slack"
book.year = 2002
book.available = True
book.publisher = "BroadwayBooks"
Set book.authors = JsInterop.NewArray()
Set author = JsInterop.Object("VbInterop.Json.Author")
author.firstName = "Tom"
author.lastName = "DeMarco"
book.authors.push(author)
package.result.books.push(book)
Next
Response.Write package.toJSON()
%>
<script language="jscript" runat="server" src="vb-interop.js"></script>
<script language="jscript" runat="server">
// Books collection
VbInterop.Json.BooksCollection = function () {
this.books = OBJ;
};
// Book class
VbInterop.Json.Book = function () {
this.title = STR;
this.year = NUM;
this.available = BOOL;
this.authors = RG;
this.publisher = STR;
};
// Author class
VbInterop.Json.Author = function () {
this.firstName = STR;
this.lastName = STR;
};
</script>
I don't really understand the workings of JS and VBs interop, but in a very rough sense it seems that:
a) Any JS features implemented via the IDispatchEx interface (see http://blogs.msdn.com/ericlippert/archive/2004/10/07/239289.aspx) cannot be used in VB.
b) JS/VB arrays do not play dice (see http://blogs.msdn.com/ericlippert/archive/2003/09/22/53061.aspx).
So, the two key features needed for JSON generation in a 'nice' fashion are:
1. New object creation (which requires a constructor) and
2. Arrays
#1 can easily be achieved by using an intermediary JS function to instantiate new objects from JS, then return them to VB:
JsInterop.Object = function ( sClass ) {
var obj = eval('new '+ sClass + '()');
obj.toJson = Object.prototype.toJSONString;
return obj;
}
#2 is slightly more fiddly. Instead of an actual array, create an object with a public .push() method and a private array variable:
JsInterop.NewArray: function () {
return new VbInterop.Array();
}
VbInterop.Array = function () {
var rg = [];
this.push = function( variant ) {
rg.push(variant);
}
this.toJson = function () {
return rg.toJSONString();
}
}
Pop() and Get/Set methods could also be added here if desired.
Note the augumentation of a .toJson() method that utilizes Douglas Crockford's json.js jsonification. This is where the disco happens.
Suffice to say it's all pretty simple, and the code is self-explanatory, so take a look if you're interested:
VB-INTEROP.JS
// vb-interop.js
// Utility code to build an object structure in VBScript that can be serialized into JSON
// VBScript-facing static class
// An intermediary for new object creation, required as we cannot directly instantiate JS classes from VBScript
var JsInterop = {
NewObject: function () {
var obj = {};
obj.toJson = Object.prototype.toJSONString;
return obj;
}
, NewArray: function () {
return new VbInterop.Array();
}
, Object: function ( sClass ) {
var obj = eval('new '+ sClass + '()');
obj.toJson = Object.prototype.toJSONString;
return obj;
}
};
// JScript-facing static class
// Interop namespace
var VbInterop = {};
// Seperate namespace for JSON class definitions
VbInterop.Json = {};
// VBScript-compatible Pseudo-array class
// Note that the actual array variable *must* be private, if it is public then VBScript will setup-the-bomb on it and launch all ships..
VbInterop.Array = function () {
var rg = [];
this.push = function( variant ) {
rg.push(variant);
}
this.toJson = function () {
return rg.toJSONString();
}
};
// Simple constants for clean data-typed class definitions
var STR = '';
var OBJ = {};
var BOOL = false;
var RG = {}; // We are using objects to emulate arrays in VBs
var NUM = -1;
var DT = ''; // No literal syntax exists for dates, so we will treat them as strings.
// Example JSON-RPC Response class
VbInterop.Json.RpcResponse = function () {
this.id = NUM;
this.result = OBJ;
this.error = OBJ;
};
JSON.JS MODIFICATIONS
object: function (x) {
if (x) {
if (x instanceof Array) {
return s.array(x);
}
/* begin vb-interop.js addition */
if (VbInterop && x instanceof VbInterop.Array) {
return x.toJson();
}
/* end vb-interop.js addition */
INTEROP-TESTS.ASP
<%@ LANGUAGE=VBScript LCID=5129 %>
<%
' VALID
Response.Write test1.property
' INVALID (Class not defined)
'Set foo = New test2
' VALID
Set foo = test2a()
Response.Write foo.property
Response.Write test3.property.[0]
Response.Write test3.property.[1]
' INVALID (Property/method not supported)
'i = 0
'Response.Write test3.property.[i]
' VALID
Set foo = test4()
Response.Write foo.get(0)
' VALID
Set foo = test5()
Response.Write foo.get(0).bar
%>
<script language="jscript" runat="server">
var test1 = {
property: 'test1'
}
var test2 = function () {
this.property = 'test2';
}
var test2a = function () {
return new test2();
}
var test3 = {
property: ['test3', 'test3b']
}
var test4 = function () {
return new test4a();
}
var test4a = function () {
var property = ['test4'];
this.get = function (ix) {
return property[ix];
}
}
var test5 = function () {
return new test5a();
}
var test5a = function () {
var property = [
{ bar: "test5" }
];
this.get = function (ix) {
return property[ix];
}
}
</script>
Finally, muchos graci to Eric Lippert for helpful responses to ignorant queries I had in on all this.