Ошибка JSON.Stringify для объектов Scripting.Dictionary - PullRequest
2 голосов
/ 16 марта 2011

Я работаю над классическим проектом ASP, где я реализовал класс 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 является объектом, поэтому он добавляет {} в строку JSON вокруг вывода stringified .Который в этом случае является пустой строкой.То, что я ожидаю показать, однако это не пустой объект.См. Ниже:

"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 вместо сбоя и возврата только пустой строки?Нужно ли «изобретать велосипед» и писать свой собственный класс Dictionary в VBScript, который действительно действует как обычный объект в ASP?

Ответы [ 3 ]

4 голосов
/ 17 марта 2011

Конструкция for...in Javascript (которая используется в сериализаторе JSON, на который вы ссылаетесь) работает только с собственными объектами JS. Для перечисления ключей Scripting.Dictionary необходимо использовать объект Enumerator , который будет перечислять ключи словаря.

Теперь метод JSON.stringify имеет отличный способ разрешить пользовательскую сериализацию, проверяя наличие метода toJSON для каждого свойства. К сожалению, вы не можете использовать новые методы для существующих объектов COM так же, как для собственных объектов JS, так что это не нужно.

Затем есть пользовательская функция строкового преобразователя, которую можно передать в качестве второго аргумента в вызов метода 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>

Конечно, переключаться между движками сценариев не очень эффективно (т.е. вызывать функцию VBS из JS и наоборот), поэтому вы, вероятно, захотите свести это к минимуму.


Также обратите внимание, что я не смог проверить это, так как у меня больше нет IIS на моей машине. Основной принцип должен работать, я не уверен на 100% в возможности передать ссылку на функцию JScript из VBScript. Возможно, вам придется написать небольшую пользовательскую функцию-обертку для вызова JSON.stringify в JScript:

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

, после чего вы можете просто настроить вызов VBScript:

<%
    Response.Write JSONStringify(oProd)
%>
1 голос
/ 17 марта 2011

В итоге я сам написал функцию для сериализации типа Dictionary.К сожалению, вам придется пойти и выполнить поиск и замену всех неудачных сериализаций словаря.({}) У меня нет времени, чтобы найти автоматизированный способ сделать это.Вы можете разветвляться на 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

Это было написано как функция для использования с аргументом replace JSON.stringify (2-й аргумент).Однако вы не можете передать функцию VBScript в качестве аргумента.(Из моего опыта) Если бы вы переписали эту функцию в JScript, вы могли бы использовать ее при вызове JSON.stringify, чтобы обеспечить правильную визуализацию словарей.Смотрите readme на BitBucket , чтобы узнать больше об этом.Вот как я это реализовал:

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

Известные проблемы:

  • Закрытие } для сериализации Dictionary будет таким же количеством отступов, что и содержащиеся в нем свойства,У меня не было времени выяснить, как с этим справиться, не добавив еще один аргумент, который я не хочу делать.
0 голосов
/ 16 марта 2011

JSON вообще не кодирует какую-либо информацию о типе.То, что JSON позволяет вам представлять, - это произвольная структура данных, включающая либо объект, либо массив значений.Любой такой объект может иметь произвольное количество именованных свойств, так что имена являются строками, а значения являются либо константой null, константами true или false, числами, строками, объектами или массивами значений.

Каким образом такая структура данных реализуется в любом конкретном языке времени выполнения, является вашей проблемой :-) Например, при десериализации JSON в Java можно использовать экземпляры ArrayList для массивов, экземпляры HashMap для объектов и нативныетипы для более простых значений.Однако может случиться так, что вы действительно хотите, чтобы объекты представляли собой определенный тип класса Java-бина.Чтобы сделать это, участвующий JSON-анализатор должен каким-то образом руководствоваться тем, какие объекты создавать.Как именно это работает, зависит от анализатора JSON и его API.

( edit - когда я сказал "вообще нет информации о типе", я имел в виду значения "объект"; явно логические значения,строки и числа имеют очевидные типы.)

...