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

26
นั่นเป็นเหตุผลที่ผู้คนควรละเว้นการยุ่งเหยิงกับวัตถุในตัวทั่วโลก (ตามที่กรอบต้นแบบทำ)
Gerardo Lima

คำตอบ:


82

เนื่องจาก JSON.stringify ได้รับการจัดส่งพร้อมกับเบราว์เซอร์บางตัวเมื่อเร็ว ๆ นี้ฉันขอแนะนำให้ใช้แทน toJSON ของ Prototype จากนั้นคุณจะตรวจสอบ 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

อาจเป็นเช่นนั้น แต่จะต้องโหลดไฟล์สคริปต์ทั้งหมดแม้ว่าจะไม่จำเป็นก็ตาม
Raphael Schweikert

11
จริงๆแล้วคำสั่งเดียวที่จำเป็นในการจัดการกับคำถามคือลบ Array.prototype.toJSON
Jean Vincent

1
ขอบคุณมาก. บริษัท ที่ฉันทำงานอยู่ในขณะนี้ยังคงใช้ต้นแบบในโค้ดส่วนใหญ่ของเราและนี่คือตัวช่วยชีวิตสำหรับการใช้ไลบรารีที่ทันสมัยมากขึ้นมิฉะนั้นทุกอย่างจะพัง
krob

1
ฉันค้นหาคำตอบนี้มาตลอด DAYS และโพสต์คำถาม SO สองข้อที่แตกต่างกันเพื่อพยายามหาคำตอบ เห็นว่านี่เป็นคำถามที่เกี่ยวข้องขณะที่ฉันกำลังพิมพ์ที่สาม ขอบคุณมาก!
Matthew Herbst

80

ฟังก์ชัน JSON.stringify () ที่กำหนดไว้ในECMAScript 5 ขึ้นไป (หน้า 201 - วัตถุ JSON รหัสหลอกหน้า 205)ใช้ฟังก์ชัน toJSON () เมื่อมีอยู่บนวัตถุ

เนื่องจาก Prototype.js (หรือไลบรารีอื่นที่คุณใช้) กำหนดฟังก์ชัน Array.prototype.toJSON () อาร์เรย์จะถูกแปลงเป็นสตริงก่อนโดยใช้ Array.prototype.toJSON () จากนั้นสตริงที่อ้างโดย JSON.stringify () ดังนั้น เครื่องหมายคำพูดพิเศษรอบอาร์เรย์ไม่ถูกต้อง

วิธีแก้ปัญหาจึงตรงไปตรงมาและไม่สำคัญ (นี่คือคำตอบของ Raphael Schweikert เวอร์ชันที่เรียบง่าย):

delete Array.prototype.toJSON

สิ่งนี้ก่อให้เกิดผลข้างเคียงของไลบรารีที่อาศัยคุณสมบัติฟังก์ชัน toJSON () สำหรับอาร์เรย์ แต่ฉันพบว่านี่เป็นความไม่สะดวกเล็กน้อยเมื่อพิจารณาถึงความเข้ากันไม่ได้กับ ECMAScript 5

ต้องสังเกตว่า JSON Object ที่กำหนดไว้ใน ECMAScript 5 นั้นถูกนำไปใช้อย่างมีประสิทธิภาพในเบราว์เซอร์สมัยใหม่ดังนั้นทางออกที่ดีที่สุดคือการปฏิบัติตามมาตรฐานและแก้ไขไลบรารีที่มีอยู่


5
นี่คือคำตอบที่กระชับที่สุดของสิ่งที่เกิดขึ้นกับการอ้างอิงพิเศษของอาร์เรย์
tmarthal

15

วิธีแก้ปัญหาที่เป็นไปได้ซึ่งจะไม่ส่งผลกระทบต่อการอ้างอิง Prototype อื่น ๆ คือ:

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

สิ่งนี้จะดูแลความเข้ากันไม่ได้ของ Array toJSON กับ JSON.stringify และยังรักษาฟังก์ชัน toJSON ไว้เนื่องจากไลบรารี Prototype อื่น ๆ อาจขึ้นอยู่กับมัน


ฉันใช้ตัวอย่างข้อมูลนี้ในเว็บไซต์ มันกำลังก่อให้เกิดปัญหา ส่งผลให้คุณสมบัติ 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
เยี่ยมมาก จนถึง Prototype 1.7 เท่านั้นที่เป็นปัญหา กรุณา
โหวตให้

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 กระทบกับค่าอาร์เรย์จะเรียกใช้ toJSON บนอาร์เรย์ซึ่งกำหนดไว้ใน Prototype และวิธีการดังกล่าวจะส่งคืนเวอร์ชันสตริงของอาร์เรย์ ดังนั้นคำพูดรอบวงเล็บอาร์เรย์

หากคุณลบ toJSON ออกจากวัตถุ Array ไลบรารี JSON ควรทำงานได้อย่างถูกต้อง หรือใช้ไลบรารี JSON


2
นี่ไม่ใช่ข้อผิดพลาดในไลบรารีเนื่องจากเป็นวิธีที่แน่นอนที่ JSON.stringify () กำหนดไว้ใน ECMAScript 5 ปัญหาเกิดขึ้นกับ prototype.js และวิธีแก้ไขคือ: ลบ Array.prototype.toJSON ซึ่งจะมีบางด้าน เอฟเฟกต์สำหรับการทำให้เป็นอนุกรม toJSON ต้นแบบ แต่ฉันพบเล็กน้อยเหล่านี้เกี่ยวกับความไม่ลงรอยกันที่ต้นแบบมีกับ ECMAScript 5
Jean Vincent

ไลบรารีต้นแบบไม่ขยาย Object.prototype แต่ Array.prototype แม้ว่าอาร์เรย์ typeof ใน JavaScript จะส่งคืน "object" ด้วย แต่ก็ไม่มี "constructor" และ Prototype เหมือนกัน ในการแก้ปัญหาคุณต้อง: "ลบ Array.prototype.toJSON;"
Jean Vincent

@Jean เพื่อความเป็นธรรม Prototype ขยายวัตถุพื้นเมืองพื้นฐานทั้งหมดรวมถึง Object ด้วย แต่โอเคฉันเห็นประเด็นของคุณอีกครั้ง :) ขอบคุณที่ช่วยให้คำตอบของฉันดีขึ้น
Bob

Prototype หยุดขยาย "Object.prototype" ไปนานแล้ว (ผมจำไม่ได้ว่ารุ่นไหน) เพื่อหลีกเลี่ยงปัญหาสำหรับ .. ตอนนี้ขยายคุณสมบัติคงที่ของ Object เท่านั้น (ซึ่งปลอดภัยกว่ามาก) เป็นเนมสเปซ: api.prototypejs.org/language/Object
Jean Vincent

Jean จริงๆแล้วมันเป็นบั๊กในห้องสมุด ถ้าออบเจ็กต์มี 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' เป็น Object คุณควรทำสิ่งนี้แทน: 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

โซลูชันที่อดทนของฉันตรวจสอบว่า 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และสุดท้ายเรียกคืน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.