Причудливость массива 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

Я не так хорошо владею Prototype, но я видел это в его документации :

Object.toJSON({"a":[1,2]})

Я не уверен, что это будет иметь ту же проблему, что и текущая кодировка.

Также есть более подробное руководство по использованию JSON с Prototype.

Пауэрлорд
источник
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
    }
}

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

polm23
источник
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и, наконец, восстановить его.

Jerska
источник