Sự kỳ lạ của mảng JSON.stringify () với Prototype.js


89

Tôi đang cố gắng tìm ra vấn đề xảy ra với việc tuần tự hóa json của mình, có phiên bản ứng dụng hiện tại và phiên bản cũ và tôi đang tìm thấy một số khác biệt đáng ngạc nhiên trong cách JSON.stringify () hoạt động (Sử dụng thư viện JSON từ json.org ).

Trong phiên bản cũ của ứng dụng của tôi:

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

cho tôi cái này;

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

trong phiên bản mới,

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

cho tôi cái này;

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

bất kỳ ý tưởng nào có thể đã thay đổi để làm cho cùng một thư viện đặt dấu ngoặc nhọn xung quanh dấu ngoặc mảng trong phiên bản mới?


4
có vẻ như nó xung đột với thư viện Prototype, mà chúng tôi đã giới thiệu trong phiên bản mới hơn. Bất kỳ ý tưởng nào về cách xâu chuỗi một đối tượng json có chứa một mảng trong Nguyên mẫu?
morgancodes

26
Đó là lý do mọi người nên tránh mangling với toàn cầu tích hợp các đối tượng (như khuôn khổ nguyên mẫu thực hiện)
Gerardo Lima

Câu trả lời:


82

Vì JSON.stringify gần đây đã được vận chuyển với một số trình duyệt, tôi khuyên bạn nên sử dụng nó thay vì toJSON của Prototype. Sau đó, bạn sẽ kiểm tra window.JSON && window.JSON.stringify và chỉ bao gồm thư viện json.org nếu không (thông qua document.createElement('script')…). Để giải quyết sự không tương thích, hãy sử dụng:

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

Không cần phải kiểm tra xem có window.JSON trong mã của riêng bạn - kịch bản json.org thực hiện điều này bản thân
zcrar70

Điều đó có thể là như vậy, nhưng sau đó toàn bộ tệp kịch bản phải được tải ngay cả khi nó không cần thiết.
Raphael Schweikert

11
Trên thực tế, câu lệnh duy nhất cần thiết để giải quyết câu hỏi là: xóa Array.prototype.toJSON
Jean Vincent

1
Cảm ơn bạn rất nhiều. Công ty tôi đang làm việc hiện tại vẫn sử dụng nguyên mẫu trong phần lớn mã của chúng tôi và đây là một cứu cánh cho việc sử dụng các thư viện hiện đại hơn, nếu không mọi thứ sẽ hỏng.
krob

1
Tôi đã tìm kiếm câu trả lời này trong DAYS và đã đăng hai câu hỏi SO khác nhau để cố gắng tìm ra. Thấy đây là một câu hỏi liên quan khi tôi đang gõ một phần ba. Cảm ơn bạn rất nhiều!
Matthew Herbst

78

Hàm JSON.stringify () được định nghĩa trong ECMAScript 5 trở lên (Trang 201 - Đối tượng JSON, mã giả Trang 205) , sử dụng hàm toJSON () khi có sẵn trên các đối tượng.

Vì Prototype.js (hoặc một thư viện khác mà bạn đang sử dụng) định nghĩa một hàm Array.prototype.toJSON (), các mảng trước tiên được chuyển đổi thành chuỗi bằng cách sử dụng Array.prototype.toJSON () sau đó chuỗi được trích dẫn bởi JSON.stringify (), do đó dấu ngoặc kép không chính xác xung quanh các mảng.

Do đó, giải pháp dễ hiểu và đơn giản (đây là phiên bản đơn giản hóa câu trả lời của Raphael Schweikert):

delete Array.prototype.toJSON

Tất nhiên, điều này tạo ra các hiệu ứng phụ trên các thư viện dựa vào thuộc tính hàm toJSON () cho các mảng. Nhưng tôi thấy đây là một sự bất tiện nhỏ do tính không tương thích với ECMAScript 5.

Cần lưu ý rằng Đối tượng JSON được định nghĩa trong ECMAScript 5 được triển khai hiệu quả trong các trình duyệt hiện đại và do đó giải pháp tốt nhất là tuân thủ tiêu chuẩn và sửa đổi các thư viện hiện có.


5
Đây là câu trả lời ngắn gọn nhất về những gì đang xảy ra với phần trích dẫn thêm của mảng.
tmarthal

15

Một giải pháp khả thi sẽ không ảnh hưởng đến các phụ thuộc Prototype khác sẽ là:

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

Điều này xử lý sự không tương thích của Array toJSON với JSON.stringify và cũng giữ lại chức năng của toJSON vì các thư viện Prototype khác có thể phụ thuộc vào nó.


Tôi đã sử dụng đoạn mã này trong một trang web. Nó đang gây ra vấn đề. Nó dẫn đến thuộc tính toJSON của mảng là không xác định. Bất kỳ gợi ý về điều đó?
Sourabh

1
Vui lòng đảm bảo rằng Array.prototype.toJSON của bạn được xác định trước khi sử dụng đoạn mã trên để xác định lại JSON.stringify. Nó hoạt động tốt trong thử nghiệm của tôi.
akkishore

2
Tôi quấn vào if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Nó đã làm việc.
Sourabh

1
Tuyệt quá. Chỉ cho đến khi Prototype 1.7 là một vấn đề. Please upvote :)
akkishore

1
Vấn đề là đối với các phiên bản <1.7
Sourabh.

9

Chỉnh sửa để chính xác hơn một chút:

Chốt mã vấn đề nằm trong thư viện JSON từ JSON.org (và các triển khai khác của đối tượng JSON của ECMAScript 5):

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

Vấn đề là thư viện Prototype mở rộng Array để bao gồm một phương thức toJSON, mà đối tượng JSON sẽ gọi trong đoạn mã trên. Khi đối tượng JSON chạm vào giá trị mảng, nó gọi tớiJSON trên mảng được xác định trong Nguyên mẫu và phương thức đó trả về phiên bản chuỗi của mảng. Do đó, dấu ngoặc kép xung quanh dấu ngoặc mảng.

Nếu bạn xóa toJSON khỏi đối tượng Mảng, thư viện JSON sẽ hoạt động bình thường. Hoặc, chỉ cần sử dụng thư viện JSON.


2
Đây không phải là một lỗi trong thư viện, vì đây là cách chính xác mà JSON.stringify () được định nghĩa trong ECMAScript 5. Vấn đề là với prototype.js và giải pháp là: xóa Array.prototype.toJSON Điều này sẽ có một số mặt hiệu ứng cho mẫu toJSON serialization, nhưng tôi thấy những nhỏ về vấn đề của sự không tương thích rằng nguyên mẫu có với ECMAScript 5.
Jean Vincent

thư viện Prototype không mở rộng Object.prototype nhưng Array.prototype, mặc dù mảng typeof trong JavaScript cũng trả về "object", chúng không có cùng một "constructor" và prototype. Để giải quyết vấn đề, bạn cần: "xóa Array.prototype.toJSON;"
Jean Vincent,

@Jean Công bằng mà nói, Prototype mở rộng tất cả các đối tượng gốc cơ bản, bao gồm cả Đối tượng. Nhưng được rồi, tôi lại thấy quan điểm của bạn :) Cảm ơn bạn đã giúp câu trả lời của tôi tốt hơn
Bob

Prototype đã ngừng mở rộng "Object.prototype" trong một thời gian dài (mặc dù tôi không nhớ phiên bản nào) để tránh cho .. trong các vấn đề. Nó bây giờ chỉ mở rộng thuộc tính tĩnh của đối tượng (đó là an toàn hơn nhiều) như là một không gian tên: api.prototypejs.org/language/Object
Jean Vincent

Jean, thực ra nó chính xác là một lỗi trong thư viện. Nếu một đối tượng có toJSON, đối tượng đó phải được gọi và kết quả của nó phải được sử dụng, nhưng nó không được trích dẫn.
grr

4

Tôi nghĩ rằng một giải pháp tốt hơn sẽ là bao gồm điều này ngay sau khi nguyên mẫu đã được tải

JSON = JSON || {};

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

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

Điều này làm cho hàm nguyên mẫu có sẵn dưới dạng JSON.stringify () và JSON.parse () tiêu chuẩn, nhưng vẫn giữ nguyên JSON.parse () nếu có sẵn, vì vậy điều này làm cho mọi thứ tương thích hơn với các trình duyệt cũ hơn.


phiên bản JSON.stringify không hoạt động nếu giá trị được truyền vào là một Đối tượng. Thay vào đó, bạn nên làm điều này: JSON.stringify = function (value) {return Object.toJSON (value); }
akkishore 12/12/12

2

Tôi không thông thạo lắm với Prototype, nhưng tôi đã thấy điều này trong tài liệu của nó :

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

Tuy nhiên, tôi không chắc liệu điều này có gặp phải vấn đề tương tự như mã hóa hiện tại hay không.

Ngoài ra còn có một hướng dẫn dài hơn về cách sử dụng JSON với Prototype.


2

Đây là mã tôi đã sử dụng cho cùng một vấn đề:

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

Bạn kiểm tra xem Prototype có tồn tại hay không, sau đó bạn kiểm tra phiên bản. Nếu phiên bản cũ sử dụng Object.toJSON (nếu được định nghĩa) trong tất cả các trường hợp khác thì dự phòng cho JSON.stringify ()


1

Đây là cách tôi đối phó với nó.

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

1

Giải pháp khoan dung của tôi sẽ kiểm tra xem Array.prototype.toJSON có gây hại cho JSON stringify hay không và giữ nó khi có thể để mã xung quanh hoạt động như mong đợi:

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

Như mọi người đã chỉ ra, điều này là do Prototype.js - cụ thể là các phiên bản trước 1.7. Tôi đã gặp trường hợp tương tự nhưng phải có mã hoạt động cho dù Prototype.js có ở đó hay không; điều này có nghĩa là tôi không thể xóa Array.prototype.toJSON vì tôi không chắc cái gì dựa vào nó. Đối với tình huống đó, đây là giải pháp tốt nhất mà tôi đã đưa ra:

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

Hy vọng rằng nó sẽ giúp một ai đó.


0

Nếu bạn không muốn giết mọi thứ và có một mã có thể sử dụng được trên hầu hết các trình duyệt, bạn có thể thực hiện theo cách này:

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

Điều này có vẻ phức tạp, nhưng điều này chỉ phức tạp để xử lý hầu hết các trường hợp sử dụng. Ý tưởng chính là ghi đè JSON.stringifyđể xóa toJSONkhỏi đối tượng được truyền dưới dạng đối số, sau đó gọi đối tượng cũ JSON.stringify, và cuối cùng khôi phục nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.