Keanehan array JSON.stringify () dengan Prototype.js

89

Saya mencoba mencari tahu apa yang salah dengan serialisasi json saya, memiliki versi aplikasi saya saat ini dan versi lama dan saya menemukan beberapa perbedaan mengejutkan dalam cara kerja JSON.stringify () (Menggunakan perpustakaan JSON dari json.org ).

Di versi lama aplikasi saya:

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

berikan aku ini;

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

di versi baru,

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

berikan aku ini;

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

tahu apa yang bisa berubah untuk membuat pustaka yang sama memberi tanda kutip di sekitar tanda kurung siku di versi baru?

kamar mayat
sumber
4
Sepertinya ada konflik dengan pustaka Prototipe, yang kami perkenalkan di versi yang lebih baru. Adakah ide untuk merangkai objek json yang berisi array di bawah Prototipe?
morgancodes
26
itulah mengapa orang harus menahan diri untuk tidak merusak objek bawaan global (seperti yang dilakukan kerangka prototipe)
Gerardo Lima

Jawaban:

82

Karena JSON.stringify telah dikirim dengan beberapa browser belakangan ini, saya akan menyarankan untuk menggunakannya daripada toJSON Prototipe. Anda kemudian akan memeriksa window.JSON && window.JSON.stringify dan hanya menyertakan pustaka json.org jika tidak (melalui document.createElement('script')…). Untuk mengatasi ketidakcocokan, gunakan:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}
Raphael Schweikert
sumber
Tidak perlu memeriksa window.JSON dalam kode Anda sendiri - skrip json.org melakukannya sendiri
zcrar70
Mungkin saja begitu, tetapi kemudian seluruh file skrip harus dimuat meskipun tidak diperlukan.
Raphael Schweikert
11
Sebenarnya, satu-satunya pernyataan yang diperlukan untuk menangani pertanyaan ini adalah: delete Array.prototype.toJSON
Jean Vincent
1
Terima kasih banyak. Perusahaan tempat saya bekerja saat ini masih menggunakan prototipe di banyak kode kami dan ini adalah penyelamat untuk menggunakan perpustakaan yang lebih modern, jika tidak semuanya akan rusak.
krob
1
Saya telah mencari jawaban ini selama DAYS, dan memposting dua pertanyaan SO yang berbeda mencoba mencari tahu. Saya melihat ini sebagai pertanyaan terkait saat saya mengetik pertanyaan ketiga. Terima kasih banyak!
Matthew Herbst
78

Fungsi JSON.stringify () yang didefinisikan dalam ECMAScript 5 dan di atasnya (Halaman 201 - Objek JSON, pseudo-code Halaman 205) , menggunakan fungsi toJSON () jika tersedia pada objek.

Karena Prototype.js (atau pustaka lain yang Anda gunakan) mendefinisikan fungsi Array.prototype.toJSON (), array pertama-tama diubah menjadi string menggunakan Array.prototype.toJSON () lalu string dikutip oleh JSON.stringify (), maka kutipan tambahan yang salah di sekitar array.

Oleh karena itu, solusinya langsung ke depan dan sepele (ini adalah versi sederhana dari jawaban Raphael Schweikert):

delete Array.prototype.toJSON

Hal ini tentu saja menghasilkan efek samping pada pustaka yang mengandalkan properti fungsi toJSON () untuk array. Tapi saya menemukan ini ketidaknyamanan kecil mengingat ketidakcocokan dengan ECMAScript 5.

Perlu dicatat bahwa Objek JSON yang ditentukan dalam ECMAScript 5 diimplementasikan secara efisien di browser modern dan oleh karena itu solusi terbaik adalah menyesuaikan dengan standar dan memodifikasi pustaka yang ada.

Jean Vincent
sumber
5
Ini adalah jawaban paling ringkas dari apa yang terjadi dengan kutipan ekstra dari larik.
tmarthal
15

Solusi yang memungkinkan yang tidak akan memengaruhi dependensi Prototipe lainnya adalah:

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;
};

Ini menangani ketidakcocokan Array toJSON dengan JSON.stringify dan juga mempertahankan fungsionalitas toJSON karena pustaka Prototipe lain mungkin bergantung padanya.

akkishore
sumber
Saya menggunakan potongan ini di situs web. Ini menyebabkan masalah. Ini menghasilkan properti toJSON array yang tidak ditentukan. Ada petunjuk tentang itu?
Sourabh
1
Harap pastikan bahwa Array.prototype.toJSON Anda ditentukan sebelum menggunakan potongan di atas untuk mendefinisikan ulang JSON.stringify. Ini berfungsi dengan baik dalam pengujian saya.
akkishore
2
Saya terbungkus if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Berhasil.
Sourabh
1
Bagus. Hanya sampai Prototipe 1.7 ini menjadi masalah. Silakan
pilih
1
Masalahnya adalah untuk versi <1.7
Sourabh
9

Edit untuk membuatnya lebih akurat:

Bit kode kunci masalah ada di pustaka JSON dari JSON.org (dan implementasi lain dari objek JSON ECMAScript 5):

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

Masalahnya adalah bahwa pustaka Prototipe memperluas Array untuk menyertakan metode toJSON, yang akan dipanggil oleh objek JSON dalam kode di atas. Saat objek JSON mencapai nilai array, ia memanggil toJSON pada array yang didefinisikan dalam Prototipe, dan metode itu mengembalikan versi string dari array. Karenanya, tanda kutip di sekitar tanda kurung siku.

Jika Anda menghapus toJSON dari objek Array, pustaka JSON akan berfungsi dengan baik. Atau, cukup gunakan perpustakaan JSON.

Bob
sumber
2
Ini bukan bug di perpustakaan, karena ini adalah cara yang tepat JSON.stringify () didefinisikan di ECMAScript 5. Masalahnya adalah dengan prototype.js dan solusinya adalah: hapus Array.prototype.toJSON Ini akan memiliki beberapa sisi efek untuk serialisasi toJSON prototipe, tetapi saya menemukan hal kecil ini sehubungan dengan ketidakcocokan yang dimiliki prototipe dengan ECMAScript 5.
Jean Vincent
perpustakaan Prototipe tidak memperluas Object.prototype tetapi Array.prototype, meskipun array typeof di JavaScript juga mengembalikan "objek", mereka tidak memiliki "konstruktor" dan prototipe yang sama. Untuk mengatasi masalah ini, Anda perlu: "menghapus Array.prototype.toJSON;"
Jean Vincent
@Jean Agar adil, Prototipe memperluas semua objek asli dasar, termasuk Objek. Tapi oke, saya mengerti maksud Anda lagi :) Terima kasih telah membantu jawaban saya menjadi lebih baik
Bob
Prototipe telah berhenti memperluas "Object.prototype" untuk waktu yang lama sekarang (saya tidak ingat versi yang mana) untuk menghindari untuk .. dalam masalah. Sekarang hanya memperluas properti statis Object (yang jauh lebih aman) sebagai namespace: api.prototypejs.org/language/Object
Jean Vincent
Jean, sebenarnya itu serangga di perpustakaan. Jika sebuah objek memiliki toJSON, itu harus dipanggil dan hasilnya harus digunakan, tetapi tidak boleh dikutip.
grr
4

Saya pikir solusi yang lebih baik adalah memasukkan ini setelah prototipe dimuat

JSON = JSON || {};

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

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

Ini membuat fungsi prototipe tersedia sebagai JSON.stringify () standar dan JSON.parse (), tetapi tetap mempertahankan JSON.parse () asli jika tersedia, jadi ini membuat segalanya lebih kompatibel dengan browser lama.

Benjamin
sumber
versi JSON.stringify tidak berfungsi jika 'nilai' yang diteruskan adalah Objek. Anda harus melakukan ini sebagai gantinya: JSON.stringify = function (value) {return Object.toJSON (value); };
akkishore
2

Saya tidak begitu fasih dengan Prototipe, tapi saya melihat ini di dokumennya :

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

Saya tidak yakin apakah ini akan memiliki masalah yang sama dengan pengkodean saat ini.

Ada juga tutorial yang lebih panjang tentang menggunakan JSON dengan Prototipe.

Powerlord
sumber
2

Ini adalah kode yang saya gunakan untuk masalah yang sama:

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

Anda memeriksa apakah Prototipe ada, lalu Anda memeriksa versinya. Jika versi lama menggunakan Object.toJSON (jika ditentukan) dalam semua kasus lainnya, fallback ke JSON.stringify ()

Memo
sumber
1

Begini cara saya menghadapinya.

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);
kamar mayat
sumber
1

Solusi toleran saya memeriksa apakah Array.prototype.toJSON berbahaya untuk stringify JSON dan menyimpannya bila memungkinkan untuk membiarkan kode di sekitarnya berfungsi seperti yang diharapkan:

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
    }
}
pronebird
sumber
1

Seperti yang ditunjukkan orang, ini karena Prototype.js - khususnya versi sebelum 1.7. Saya memiliki situasi yang sama tetapi harus memiliki kode yang beroperasi apakah Prototype.js ada atau tidak; ini berarti saya tidak bisa begitu saja menghapus Array.prototype.toJSON karena saya tidak yakin apa yang bergantung padanya. Untuk situasi itu, ini adalah solusi terbaik yang saya buat:

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
    }
}

Semoga bisa membantu seseorang.

polm23
sumber
0

Jika Anda tidak ingin mematikan semuanya, dan memiliki kode yang baik-baik saja di sebagian besar browser, Anda dapat melakukannya dengan cara ini:

(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));

Ini tampaknya rumit, tetapi rumit hanya untuk menangani sebagian besar kasus penggunaan. Ide utamanya adalah menimpa JSON.stringifyuntuk menghapus toJSONdari objek yang diteruskan sebagai argumen, kemudian memanggil yang lama JSON.stringify, dan akhirnya memulihkannya.

Jerska
sumber