可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am working on an ASP classic project where I have implemented the JScript JSON class found here. It is able to interop with both VBScript and JScript and is almost exactly the code provided at json.org. I am required to use VBScript for this project by the manager of my team.
It works very well on primitives and classes defined within ASP. But I have need for Dictionary objects which from my knowledge are only available through COM interop. (via Server.CreateObject("Scripting.Dictionary")
) I have the following class which represents a product: (ProductInfo.class.asp)
<%
Class ProductInfo
Public ID
Public Category
Public PriceUS
Public PriceCA
Public Name
Public SKU
Public Overview
Public Features
Public Specs
End Class
%>
The Specs
property is a Dictionary of key:value pairs. Here's how I'm serializing it: (product.asp)
<%
dim oProd
set oProd = new ProductInfo
' ... fill in properties
' ... output appropriate headers and stuff
Response.write( JSON.stringify( oProd ) )
%>
When I pass an instance of ProductInfo
to JSON.Stringify
(as seen above) I get something like the following:
{
"id": "1547",
"Category": {
"id": 101,
"Name": "Category Name",
"AlternateName": "",
"URL": "/category_name/",
"ParentCategoryID": 21
},
"PriceUS": 9.99,
"PriceCA": 11.99,
"Name": "Product Name",
"SKU": 3454536,
"Overview": "Lorem Ipsum dolor sit amet..",
"Features": "Lorem Ipsum dolor sit amet..",
"Specs": {}
}
As you can see, the Specs
property is an empty object. I believe that the JSON stringify method knows that the Specs
property is an object, so it appends the {}
to the JSON string around the stringified output. Which in this case is an empty string. What I expect it to show, however is not an empty object. See below:
"Specs": {
"foo":"bar",
"baz":1,
"etc":"..."
}
I believe the problem area of the JSON library is here: (json2.asp)
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
I postulate that the problem with the above code is that it assumes that all objects inherit from the Object
class. (The one that provides hasOwnProperty
) However I think that it's likely that COM objects don't inherit from the Object
class — or at least the same Object
class. Or at least don't implement whatever interface is required to do for ... in
on them.
Update: While I feel it is irrelevant for the question to be answered — I expect some sort of web client to request (via http) the JSON representation of this object or a collection of this object.
tl;dr The question: What should I do to make it so that the Scripting.Dictionary
can be output properly as JSON instead of failing and returning just an empty string? Do I need to 'reinvent the wheel' and write my own Dictionary
class in VBScript that does act as a normal object in ASP?
回答1:
Javascript’s for...in
construct (which is used in the JSON serializer you refer to) only works on native JS objects. To enumerate a Scripting.Dictionary
’s keys, you need to use an Enumerator object, which will enumerate the keys of the Dictionary.
Now the JSON.stringify
method has a nifty way of allowing custom serialization, by checking for the presence of a toJSON
method on each property. Unfortunately, you can’t tack new methods on existing COM objects the way you can on native JS objects, so that’s a no-go.
Then there’s the custom stringifier function that can be passed as second argument to the stringify
method call. That function will be called for each object that needs to be stringified, even for each nested object. I think that could be used here.
One problem is that (AFAIK) JScript is unable to differentiate VBScript types on its own. To JScript, any COM or VBScript object has typeof === 'object'
. The only way I know of getting that information across, is defining a VBS function that will return the type name.
Since the execution order for classic ASP files is as follows:
<script>
blocks with non-default script languages (in your case, JScript)
<script>
blocks with the default script language (in your case, VBScript)
<% ... %>
blocks, using the default script language (in your case, VBScript)
The following could work — but only when the JSON.stringify
call is done within <% ... %>
brackets, since that’s the only time when both JScript and VBScript <script>
sections would both have been parsed and executed.
The final function call would be this:
<%
Response.Write JSON.stringify(oProd, vbsStringifier)
%>
In order to allow JScript to check the type of a COM object, we'd define a VBSTypeName function:
<script language="VBScript" runat="server">
Function VBSTypeName(Obj)
VBSTypeName = TypeName(Obj)
End Function
</script>
And here we have the full implementation of the vbsStringifier that is passed along as second parameter to JSON.stringify:
<script language="JScript" runat="server">
function vbsStringifier(holder, key, value) {
if (VBSTypeName(value) === 'Dictionary') {
var result = '{';
for(var enr = new Enumerator(value); !enr.atEnd(); enr.moveNext()) {
key = enr.item();
result += '"' + key + '": ' + JSON.stringify(value.Item(key));
}
result += '}';
return result;
} else {
// return the value to let it be processed in the usual way
return value;
}
}
</script>
Of course, switching back and forth between scripting engines isn’t very efficient (i.e. calling a VBS function from JS and vice versa), so you probably want to try to keep that to a minimum.
Also note that I haven’t been able to test this, since I no longer have IIS on my machine. The basic principle should work, I’m not 100% certain of the possibility to pass a JScript function reference from VBScript. You might have to write a small custom wrapper function for the JSON.stringify call in JScript:
<script runat="server" language="JScript">
function JSONStringify(object) {
return JSON.stringify(object, vbsStringifier);
}
</script>
after which you can simply adjust the VBScript call:
<%
Response.Write JSONStringify(oProd)
%>
回答2:
I ended up writing a function to serialize the Dictionary type myself. Unfortunately, you'll have to go in and do a find & replace on any failed dictionary serializations. ({}
) I haven't the time to figure out an automated way to do this. You're welcome to fork it on BitBucket.
Function stringifyDictionary( key, value, sp )
dim str, val
Select Case TypeName( sp )
Case "String"
sp = vbCrLf & sp
Case "Integer"
sp = vbCrLf & Space(sp)
Case Else
sp = ""
End Select
If TypeName( value ) = "Dictionary" Then
str = """" & key & """:{" & sp
For Each k in value
val = value.Item(k)
If Not Right(str, 1+len(sp)) = "{" & sp And Not Right(str, 1+len(sp)) = "," & sp Then
str = str & "," & sp
End If
str = str & """" & k & """: "
If TypeName( val ) = "String" Then
If val = "" Then
str = str & "null"
Else
str = str & """" & escapeJSONString( val ) & """"
End If
Else
str = str & CStr( val )
End If
Next
str = str & sp & "}"
stringifyDictionary = str
Else
stringifyDictionary = value
End If
End Function
Function escapeJSONString( str )
escapeJSONString = replace(replace(str, "\", "\\"), """", "\""")
End Function
This was written as a function to use with JSON.stringify's replace
argument (2nd arg). However you cannot pass a VBScript function as an argument. (From my experience) If you were to rewrite this function in JScript you could use it when you're calling JSON.stringify to ensure that Dictionaries do get rendered properly. See the readme on BitBucket for more on that. Here's how I implemented it:
dim spaces: spaces = 2
dim prodJSON: prodJSON = JSON.stringify( oProduct, Nothing, spaces)
prodJSON = replace(prodJSON, """Specs"": {}", stringifyDictionary("Specs", oProduct.Specs, spaces * 2))
Known issues:
- The closing
}
for the Dictionary
serialization will be the same number of indentations as the properties it contains. I didn't have time to figure out how to deal with that without adding another argument which I don't want to do.
回答3:
JSON does not inherently encode any type information at all. What JSON enables you to represent is an arbitrary data structure involving either an object or an array of values. Any such object may have an arbitrary number of named properties such that the names are strings and the values are either the constant null
, the constants true
or false
, numbers, strings, objects, or arrays of values.
How such a data structure is realized in any given programming language runtime is your problem :-) For example, when de-serializing JSON into Java, one might use ArrayList instances for arrays, HashMap instances for objects, and native types for simpler values. However, it might be that you really want the objects to be some particular type of Java bean class. To do that, the JSON parser involved would have to be somehow guided as to what sort of objects to instantiate. Exactly how that works depends on the JSON parser and its APIs.
(edit — when I said "no type information at all", I meant for "object" values; clearly booleans, strings, and numbers have obvious types.)