【问题标题】:JSON.Stringify fails on Scripting.Dictionary objectsJSON.Stringify 在 Scripting.Dictionary 对象上失败
【发布时间】:2011-07-16 18:39:44
【问题描述】:

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

它在 ASP 中定义的原语和类上工作得很好。但我需要 Dictionary 对象,据我所知,这些对象只能通过 COM 互操作获得。 (通过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 所需的任何接口。

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

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

【问题讨论】:

  • JSON 输出对我来说看起来非常好。目前尚不清楚问题是什么; JSON(字符串形式)将如何被消费?
  • @Pointy 我更新了 OP。我添加了 JSON 字符串的预期用途并澄清了问题所在。

标签: javascript json com asp-classic vbscript


【解决方案1】:

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

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

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

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

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

  • &lt;script&gt; 具有非默认脚本语言(在您的情况下为 JScript)的块
  • &lt;script&gt; 具有默认脚本语言(在您的情况下为 VBScript)的块
  • &lt;% ... %&gt; 块,使用默认脚本语言(在您的情况下为 VBScript)

以下方法可行——但JSON.stringify 调用在&lt;% ... %&gt; 括号内完成时,因为这是唯一一次同时解析JScript 和VBScript &lt;script&gt; 部分并执行。

最终的函数调用是这样的:

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

【讨论】:

  • 非常感谢!这有助于我理解这一切如何运作的复杂性。我知道关于 JScript 的部分不知道具体类型。但是处理事情的顺序对我来说是新的信息。此外,我不认为在我自己的 Dictionary 字符串化函数中递归到另一个 JSON.stringify 调用。请参阅我的答案以了解我是如何实现的。我必须克服 VBScript 无法将函数作为参数传递的问题。如果参数是 JScript 函数,你知道这是否仍然适用?
  • 没问题,这是一个有趣的小谜题,即使经典的 ASP 有点过时......正如我在回答中所写的,我担心 VBScript 将无法将函数作为参数传递。但是,您可以在 JScript 中编写一个包装函数,该函数将愉快地传递对 JScript 函数的引用。我会更新我的答案。
  • 我正在等待一些空闲时间来测试您的解决方案。虽然我的有效我想看看你的是否有效——这是一个更好的解决方案。如果是这样,那么我希望您允许将您的解决方案作为补丁提交给 ASP Xtreme Evolution 的 JSON2 解析器。
  • 虽然我仍然没有测试你的答案,但我觉得这是解决问题的正确方法。感谢您的努力。
【解决方案2】:

我最终自己编写了一个函数来序列化 Dictionary 类型。不幸的是,您将不得不对任何失败的字典序列化进行查找和替换。 ({}) 我没有时间想出一种自动化的方法来做到这一点。欢迎您在BitBucket 上 fork。

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 参数(第二个参数)一起使用。但是,您不能将 VBScript 函数作为参数传递。 (根据我的经验)如果您要在 JScript 中重写此函数,您可以在调用 JSON.stringify 时使用它,以确保正确呈现字典。有关更多信息,请参阅BitBucket 上的自述文件。以下是我的实现方式:

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

已知问题:

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

【讨论】:

    【解决方案3】:

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

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

    edit — 当我说“根本没有类型信息”时,我指的是“对象”值;显然布尔值、字符串和数字都有明显的类型。)

    【讨论】:

    • 我已将我期望ProductInfo 实例的Specs 属性输出到OP。
    • @sholsinger 好吧,我仍然不清楚您的序列化发生在哪个域中。当有一个对象值属性时,一个正常工作的 JSON 序列化器肯定会不会 在对象的字符串版本周围发出“{}” - 它将完全按照您的描述递归地编码对象。我建议也许“规格”属性实际上并不具有您认为的价值。无论是这样,还是在存在该数据结构的域中,“Specs”对象由于某种原因无法序列化。
    • 我为 OP 的序列化在何处以及如何发生添加了更多上下文。我同意 Specs 对象由于某种原因不能被序列化。我想知道为什么 VBScript 中的 Scripting.Dictionary 不能序列化?我想的问题是我是否应该创建自己的 Dictionary 类来替换 Scripting.Dictionary 然后它是可序列化的。
    • @sholsinger 好吧,我不能说为什么不能序列化对象,因为我不是 VBScript 程序员。看起来确实很奇怪,但 VBScript 比 JSON 早很多(以及 JSON 作为一种交换格式),所以也许 JSON 序列化的东西真的不符合标准。
    猜你喜欢
    • 2013-09-14
    • 2012-06-22
    • 1970-01-01
    • 2019-12-11
    • 2020-08-04
    • 2021-06-12
    • 1970-01-01
    • 2014-05-06
    • 2012-12-01
    相关资源
    最近更新 更多