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, що містить масив під прототипом?
morgancodes

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 робить це сам
zcrar70

Це може бути так, але тоді потрібно завантажити весь файл сценарію, навіть якщо він не буде потрібний.
Рафаель Швайкерт,

11
Власне, єдиним твердженням, необхідним для вирішення питання, є: видалити Array.prototype.toJSON
Жан Вінсент

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

1
Я шукав цю відповідь протягом DAYS і опублікував два різні запитання SO, намагаючись це зрозуміти. Побачив це як відповідне питання, оскільки я друкував третє. Дуже дякую!
Метью Гербст,

78

Функція JSON.stringify (), визначена в ECMAScript 5 і вище (Сторінка 201 - Об'єкт JSON, псевдокод Сторінка 205) , використовує функцію JSON (), коли доступна для об'єктів.

Оскільки 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 не визначено. Будь-які вказівки на це?
Sourabh

1
Будь ласка, переконайтеся, що ваш Array.prototype.toJSON визначений, перш ніж використовувати наведений вище фрагмент для перевизначення JSON.stringify. Це добре працює в моєму тесті.
akkishore

2
Я загорнув у if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Це спрацювало.
Sourabh

1
Чудово. Лише до прототипу 1.7 це питання. Будь ласка, проголосуйте :)
akkishore

1
Випуск стосується версій <1.7
Sourabh

9

Редагуйте, щоб зробити трохи точнішим:

Біт ключового коду проблеми знаходиться в бібліотеці JSON з JSON.org (та інших реалізаціях об'єкта JSON ECMAScript 5):

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

Проблема полягає в тому, що бібліотека Prototype розширює Array, включаючи метод toJSON, який об'єкт JSON буде викликати в коді вище. Коли об'єкт JSON потрапляє у значення масиву, він викликає JSON у масиві, який визначено у прототипі, і цей метод повертає строкову версію масиву. Отже, лапки навколо дужок масиву.

Якщо видалити 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 Щоб бути справедливим, Prototype розширює всі базові рідні об'єкти, включаючи Object. Але добре, я знову бачу вашу думку :) Дякую, що допомогли моїй відповіді бути кращою
Боб

Прототип вже давно припинив розширення "Object.prototype" (хоча не пам'ятаю, яку версію), щоб уникнути проблем у форматі ... Тепер він лише розширює статичні властивості Object (що набагато безпечніше) як простір імен: 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 не працює, якщо передане значення 'value' є об'єктом. Натомість слід зробити це: JSON.stringify = function (value) {return Object.toJSON (value); };
akkishore

2

Я не так вільно володію прототипом, але я бачив це в його документах :

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

Але я не впевнений, чи матиме це таку саму проблему, як у поточного кодування.

Також є довший підручник про використання JSON з прототипом.


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.