Причудливость массива JSON.stringify () с Prototype.js


89

Я пытаюсь выяснить, что пошло не так с моей сериализацией json, имею текущую версию моего приложения и старую, и нахожу некоторые удивительные различия в том, как работает JSON.stringify () (Использование библиотеки JSON с json.org ).

В старой версии моего приложения:

 JSON.stringify({"a":[1,2]})

дает мне это;

"{\"a\":[1,2]}"

в новой версии,

 JSON.stringify({"a":[1,2]})

дает мне это;

"{\"a\":\"[1, 2]\"}"

есть идеи, что можно было изменить, чтобы та же библиотека помещала кавычки вокруг скобок массива в новой версии?


4
похоже, это конфликт с библиотекой Prototype, которую мы представили в более новой версии. Есть идеи, как преобразовать объект json, содержащий массив в Prototype?
morgancodes 02

26
вот почему люди должны воздерживаться от манипуляций с глобальными встроенными объектами (как это делает фреймворк прототипов)
Херардо Лима,

Ответы:


82

Поскольку JSON.stringify в последнее время поставляется с некоторыми браузерами, я бы предложил использовать его вместо toJSON прототипа. Затем вы должны проверить window.JSON && window.JSON.stringify и включить только библиотеку json.org в противном случае (через document.createElement('script')…). Чтобы устранить несовместимости, используйте:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}

Нет необходимости проверять window.JSON в вашем собственном коде - скрипт
json.org

Это может быть так, но тогда должен быть загружен весь файл сценария, даже если он не понадобится.
Рафаэль Швейкерт

11
Фактически, единственное утверждение, необходимое для решения этого вопроса: delete Array.prototype.toJSON
Жан Винсент

1
Огромное спасибо. Компания, в которой я сейчас работаю, в настоящее время по-прежнему использует прототип в большей части нашего кода, и это было спасением для использования более современных библиотек, иначе все могло сломаться.
krob

1
Я искал этот ответ для ДНЕЙ и опубликовал два разных вопроса SO, пытаясь понять это. Я воспринимал это как связанный вопрос, когда набирал третий. Спасибо огромное!
Мэтью Хербст,

78

Функция JSON.stringify (), определенная в ECMAScript 5 и выше (стр. 201 - объект JSON, псевдокод, стр. 205) , использует функцию toJSON (), если она доступна для объектов.

Поскольку Prototype.js (или другая используемая вами библиотека) определяет функцию Array.prototype.toJSON (), массивы сначала преобразуются в строки с помощью Array.prototype.toJSON (), а затем строки, цитируемые JSON.stringify (), следовательно, неправильные лишние кавычки вокруг массивов.

Поэтому решение простое и тривиальное (это упрощенная версия ответа Рафаэля Швейкерта):

delete Array.prototype.toJSON

Это, конечно, вызывает побочные эффекты для библиотек, которые полагаются на свойство функции toJSON () для массивов. Но я считаю это незначительным неудобством, учитывая несовместимость с ECMAScript 5.

Следует отметить, что объект JSON, определенный в ECMAScript 5, эффективно реализован в современных браузерах, и поэтому лучшим решением является соответствие стандарту и изменение существующих библиотек.


5
Это наиболее краткий ответ на вопрос, что происходит с дополнительным цитированием массива.
tmarthal

15

Возможное решение, которое не повлияет на другие зависимости прототипа:

var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
    var _array_tojson = Array.prototype.toJSON;
    delete Array.prototype.toJSON;
    var r=_json_stringify(value);
    Array.prototype.toJSON = _array_tojson;
    return r;
};

Это позаботится о несовместимости массива toJSON с JSON.stringify, а также сохранит функциональность toJSON, поскольку от него могут зависеть другие библиотеки прототипов.


Я использовал этот фрагмент на веб-сайте. Это вызывает проблемы. Это приводит к тому, что свойство массива toJSON не определено. Есть указатели на это?
Сураб

1
Убедитесь, что ваш Array.prototype.toJSON определен, прежде чем использовать приведенный выше фрагмент для переопределения JSON.stringify. В моем тесте он отлично работает.
Akkishore 05

2
Я завернулся в if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Это сработало.
Сураб

1
Отлично. Только до Prototype 1.7 это проблема. Пожалуйста, проголосуйте за :)
Akkishore

1
Проблема для версий <1.7
Сураб

9

Отредактируйте, чтобы сделать немного точнее:

Проблемный ключевой бит кода находится в библиотеке JSON от JSON.org (и других реализациях объекта JSON ECMAScript 5):

if (value && typeof value === 'object' &&
  typeof value.toJSON === 'function') {
  value = value.toJSON(key);
}

Проблема в том, что библиотека Prototype расширяет массив, чтобы включить метод toJSON, который объект JSON будет вызывать в приведенном выше коде. Когда объект JSON достигает значения массива, он вызывает JSON в массиве, который определен в Prototype, и этот метод возвращает строковую версию массива. Следовательно, кавычки вокруг скобок массива.

Если вы удалите toJSON из объекта Array, библиотека JSON должна работать правильно. Или просто используйте библиотеку JSON.


2
Это не ошибка в библиотеке, потому что именно так JSON.stringify () определен в ECMAScript 5. Проблема связана с prototype.js, и ее решение: удалить Array.prototype.toJSON У этого будет некоторая сторона эффекты для сериализации прототипа в JSON, но я счел их незначительными в отношении несовместимости прототипа с ECMAScript 5.
Жан Винсент

библиотека Prototype не расширяет Object.prototype, а Array.prototype, хотя массив typeof в JavaScript также возвращает «объект», у них нет того же «конструктора» и прототипа. Для решения проблемы вам необходимо: «удалить Array.prototype.toJSON;»
Jean Vincent

@Jean Честно говоря, Prototype расширяет все базовые собственные объекты, включая Object. Но хорошо, я снова понимаю вашу точку зрения :) Спасибо за то, что помогли мне улучшить мой ответ
Боб

Prototype давно прекратил расширять Object.prototype (правда, не помню, какую версию), чтобы избежать проблем с for .. in. Теперь он расширяет только статические свойства объекта (что намного безопаснее) в качестве пространства имен: api.prototypejs.org/language/Object
Жан Винсент

Жан, на самом деле это именно ошибка в библиотеке. Если объект имеет toJSON, он должен быть вызван и его результат должен быть использован, но не должен цитироваться.
grr

4

Я думаю, что лучшим решением было бы включить это сразу после загрузки прототипа

JSON = JSON || {};

JSON.stringify = function(value) { return value.toJSON(); };

JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };

Это делает функцию прототипа доступной как стандартные JSON.stringify () и JSON.parse (), но сохраняет собственный JSON.parse (), если он доступен, поэтому это делает вещи более совместимыми со старыми браузерами.


версия JSON.stringify не работает, если переданное значение является объектом. Вместо этого вы должны сделать это: JSON.stringify = function (value) {return Object.toJSON (value); };
Akkishore,


2

Это код, который я использовал для той же проблемы:

function stringify(object){
      var Prototype = window.Prototype
      if (Prototype && Prototype.Version < '1.7' &&
          Array.prototype.toJSON && Object.toJSON){
              return Object.toJSON(object)
      }
      return JSON.stringify(object)
}

Вы проверяете, существует ли прототип, затем проверяете версию. Если старая версия использует Object.toJSON (если определено), во всех остальных случаях используйте JSON.stringify ()


1

Вот как я с этим справляюсь.

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);

1

Мое терпимое решение проверяет, вреден ли Array.prototype.toJSON для JSON stringify, и сохраняет его, когда это возможно, чтобы окружающий код работал должным образом:

var dummy = { data: [{hello: 'world'}] }, test = {};

if(Array.prototype.toJSON) {
    try {
        test = JSON.parse(JSON.stringify(dummy));
        if(!test || dummy.data !== test.data) {
            delete Array.prototype.toJSON;
        }
    } catch(e) {
        // there only hope
    }
}

1

Как отмечали люди, это связано с Prototype.js, в частности с версиями до 1.7. У меня была похожая ситуация, но мне требовался код, который работал бы вне зависимости от того, был там Prototype.js или нет; это означает, что я не могу просто удалить Array.prototype.toJSON, поскольку я не уверен, что от него зависит. В этой ситуации это лучшее решение, которое я придумал:

function safeToJSON(item){ 
    if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
        return JSON.stringify(item); //sane behavior
    } else { 
        return item.toJSON(); // Prototype.js nonsense
    }
}

Надеюсь, это кому-то поможет.


0

Если вы не хотите убивать все, и у вас есть код, который подходит для большинства браузеров, вы можете сделать это следующим образом:

(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
  if (true ||typeof (Prototype) !== 'undefined') {
    // First, ensure we can access the prototype of an object.
    // See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
    if(typeof (Object.getPrototypeOf) === 'undefined') {
      if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          return object.__proto__;
        };
      } else {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          // May break if the constructor has been changed or removed
          return object.constructor ? object.constructor.prototype : undefined;
        }
      }
    }

    var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
    JSON.stringify = function stringify (obj) {
      var obj_prototype = Object.getPrototypeOf(obj),
          old_json = obj_prototype.toJSON, // We save the toJSON of the object
          res = null;
      if (old_json) { // If toJSON exists on the object
        obj_prototype.toJSON = undefined;
      }
      res = _json_stringify.apply(this, arguments);
      if (old_json)
        obj_prototype.toJSON = old_json;
      return res;
    };
  }
}.call(this));

Это кажется сложным, но это сложно только для большинства случаев использования. Основная идея состоит в том, JSON.stringifyчтобы удалить toJSONиз объекта, переданного в качестве аргумента, затем вызвать старый JSON.stringifyи, наконец, восстановить его.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.