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

时间:2011-03-16 17:00:10

标签: javascript json com asp-classic vbscript

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

它在ASP中定义的基元和类上非常有效。但是我需要Dictionary对象,据我所知,这些对象只能通过COM interop获得。 (通过Server.CreateObject("Scripting.Dictionary"))我有以下代表产品的类:(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)

<%
dim oProd
set oProd = new ProductInfo
' ... fill in properties
' ... output appropriate headers and stuff
Response.write( JSON.stringify( oProd ) )
%>

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

{
    "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字符串。在这种情况下,这是一个空字符串。我希望它展示的不是一个空的对象。见下文:

"Specs": {
    "foo":"bar",
    "baz":1,
    "etc":"..."
}

我相信JSON库的问题区域在这里:(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类继承 - 或者至少相同 Object类。或者至少不要在它们上面实现for ... in所需的任何接口。

更新:虽然我认为这个问题无关紧要 - 我希望某种Web客户端可以请求(通过http)此对象的JSON表示或此对象的集合

tl; dr问题:我该怎么做才能使Scripting.Dictionary能够正确输出为JSON而不是失败而只返回一个空字符串?我是否需要'重新发明轮子'并在VBScript中编写我自己的Dictionary类, 在ASP中作为普通对象?

3 个答案:

答案 0 :(得分:4)

Javascript的for...in构造(在您引用的JSON序列化程序中使用)仅适用于本机JS对象。要枚举Scripting.Dictionary的键,您需要使用Enumerator对象,它将枚举词典的键。

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

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

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

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

    带有非默认脚本语言的
  • <script>块(在您的情况下为JScript)
  • <script>阻止使用默认脚本语言(在您的情况下为VBScript)
  • <% ... %>阻止,使用默认脚本语言(在您的情况下为VBScript)

以下内容可以正常运行 - 但JSON.stringify调用是在<% ... %>括号内完成的,只有 ,因为这是JScript和VBScript <script>的唯一时间部分都将被解析和执行。

最后的函数调用将是:

<%
    Response.Write JSON.stringify(oProd, vbsStringifier)
%>

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

<script language="VBScript" runat="server">
    Function VBSTypeName(Obj)
        VBSTypeName = TypeName(Obj)
    End Function
</script>

这里我们有完整的vbsStringifier实现,它作为第二个参数传递给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函数,反之亦然),所以你可能想尝试将其保持在最低限度。


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

<script runat="server" language="JScript">
    function JSONStringify(object) {
        return JSON.stringify(object, vbsStringifier);
    }
</script>

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

<%
    Response.Write JSONStringify(oProd)
%>

答案 1 :(得分:1)

我最后编写了一个函数来自己序列化Dictionary类型。不幸的是,你必须进去做一个发现&amp;替换任何失败的字典序列化。 ({})我没有时间找出一种自动化的方法。欢迎您将其分配到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

这是作为与JSON.stringify的replace参数(第2个arg)一起使用的函数编写的。但是,您无法将VBScript函数作为参数传递。 (根据我的经验)如果您要在JScript中重写此函数,则可以在调用JSON.stringify时使用它以确保Dictionaries正确呈现。有关详细信息,请参阅BitBucket上的自述文件。这是我实现它的方式:

dim spaces: spaces = 2
dim prodJSON: prodJSON = JSON.stringify( oProduct, Nothing, spaces)
prodJSON = replace(prodJSON, """Specs"": {}", stringifyDictionary("Specs", oProduct.Specs, spaces * 2))

已知问题:

  • }序列化的结束Dictionary将与其包含的属性具有相同数量的缩进。我没有时间弄清楚如何处理这个问题而不添加另一个我不想做的论点。

答案 2 :(得分:0)

JSON根本不会对任何类型信息进行编码。 JSON使您能够表示的是涉及对象或值数组的任意数据结构。任何此类对象都可以具有任意数量的命名属性,以使名称为字符串,值为常量null,常量truefalse,数字,字符串,对象,或数值数组。

如何在任何给定的编程语言运行时实现这样的数据结构是你的问题:-)例如,当将JSON反序列化为Java时,可以使用ArrayList实例用于数组,HashMap实例用于对象,以及本机类型用于更简单的价值观。但是,您可能真的希望对象成为某种特定类型的Java bean类。要做到这一点,必须以某种方式引导所涉及的JSON解析器以实例化哪种对象。具体如何工作取决于JSON解析器及其API。

编辑 - 当我说“根本没有类型信息”时,我的意思是“对象”值;显然布尔值,字符串和数字都有明显的类型。)