JSON.Stringify 在 Scripting.Dictionary 对象上失败

2022-01-14 00:00:00 json com asp-classic javascript vbscript

我正在开发一个 ASP 经典项目,我在其中实现了找到的 JScript JSON 类 这里.它能够与 VBScript 和 JScript 互操作,并且几乎完全是 json.org 提供的代码.我的团队经理要求我在这个项目中使用 VBScript.

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.

它在 ASP 中定义的原语和类上工作得很好.但我需要 Dictionary 对象,据我所知,这些对象只能通过 COM 互操作获得.(通过 Server.CreateObject("Scripting.Dictionary"))我有以下代表产品的类:(ProductInfo.class.asp)

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
%>

Specs 属性是键值对的字典.这是我序列化它的方式:(product.asp)

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 ) )
%>

当我将 ProductInfo 的实例传递给 JSON.Stringify(如上所示)时,我得到如下内容:

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": {}
}

如您所见,Specs 属性是一个空对象.我相信 JSON stringify 方法知道 Specs 属性是一个对象,因此它将 {} 附加到 stringified 周围的 JSON 字符串输出.在这种情况下是一个空字符串.然而,我期望它显示的不是一个空对象.见下文:

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":"..."
}

相信JSON库的问题区域在这里:(json2.asp)

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);
        }
    }
}

我假设上述代码的问题在于它假定所有对象都继承自 Object 类.(提供 hasOwnProperty 的那个)但是我认为 COM 对象很可能不是从 Object 类继承的 —或者至少是 same Object 类.或者至少不要实现对它们执行 for ... in 所需的任何接口.

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.

更新:虽然我觉得回答这个问题无关紧要——我希望某种 Web 客户端(通过 http)请求该对象的 JSON 表示或该对象的集合.

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 问题:我应该怎么做才能使 Scripting.Dictionary 可以正确输出为 JSON 而不是失败并仅返回一个空细绳?我是否需要重新发明轮子"并在 VBScript 中编写自己的 Dictionary 类 在 ASP 中充当普通对象?

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?

推荐答案

Javascript 的 for...in 构造(在你提到的 JSON 序列化器中使用)仅适用于原生 JS 对象.要枚举 Scripting.Dictionary 的键,您需要使用 Enumerator 对象,它将枚举 Dictionary 的键.

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.

现在,JSON.stringify 方法有一种允许自定义序列化的好方法,通过检查每个属性上是否存在 toJSON 方法.不幸的是,您不能像在本机 JS 对象上那样在现有 COM 对象上添加新方法,所以这是不行的.

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.

然后是自定义字符串化函数,它可以作为第二个参数传递给 stringify 方法调用.将为每个需要字符串化的对象调用该函数,即使对于每个嵌套对象也是如此.我认为可以在这里使用.

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.

一个问题是(AFAIK)JScript 无法自行区分 VBScript 类型.对于 JScript,任何 COM 或 VBScript 对象都有 typeof === 'object'.我知道获取该信息的唯一方法是定义一个返回类型名称的 VBS 函数.

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.

由于经典ASP文件的执行顺序如下:

Since the execution order for classic ASP files is as follows:

  • <script> 具有非默认脚本语言(在您的情况下为 JScript)的块
  • <script> 具有默认脚本语言(在您的情况下为 VBScript)的块
  • <% ... %> 块,使用默认脚本语言(在您的情况下为 VBScript)
  • <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)

以下方法可以工作——但仅当 JSON.stringify 调用在 <% ... %> 括号内完成,因为这是唯一一次同时解析和执行 JScript 和 VBScript <script> 部分.

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)
%>

为了让 JScript 能够检查 COM 对象的类型,我们定义了一个 VBSTypeName 函数:

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>

这里我们有了 vbsStringifier 的完整实现,它作为第二个参数传递给 JSON.stringify:

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>

当然,在脚本引擎之间来回切换效率不高(即从 JS 调用 VBS 函数,反之亦然),因此您可能希望尽量减少这种情况.

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.

另外请注意,我无法对此进行测试,因为我的机器上不再有 IIS.基本原理应该有效,我不能 100% 确定从 VBScript 传递 JScript 函数引用的可能性.您可能需要为 JScript 中的 JSON.stringify 调用编写一个小的自定义包装函数:

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>

之后您可以简单地调整 VBScript 调用:

after which you can simply adjust the VBScript call:

<%
    Response.Write JSONStringify(oProd)
%>

相关文章