Có thể xâu chuỗi một Lỗi bằng cách sử dụng JSON.opesify không?


329

Tái tạo vấn đề

Tôi đang gặp vấn đề khi cố gắng truyền thông báo lỗi xung quanh bằng cách sử dụng ổ cắm web. Tôi có thể sao chép vấn đề tôi đang gặp phải JSON.stringifyđể sử dụng cho đối tượng rộng hơn:

// node v0.10.15
> var error = new Error('simple error message');
    undefined

> error
    [Error: simple error message]

> Object.getOwnPropertyNames(error);
    [ 'stack', 'arguments', 'type', 'message' ]

> JSON.stringify(error);
    '{}'

Vấn đề là tôi kết thúc với một đối tượng trống rỗng.

Những gì tôi đã thử

Trình duyệt

Trước tiên tôi đã thử rời node.js và chạy nó trong các trình duyệt khác nhau. Chrome phiên bản 28 cho tôi kết quả tương tự, và thật thú vị, Firefox ít nhất cũng cố gắng nhưng bỏ qua thông báo:

>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}

Chức năng thay thế

Sau đó tôi nhìn vào Error.prototype . Nó cho thấy nguyên mẫu chứa các phương thức như toStringtoSource . Biết rằng các hàm không thể được xâu chuỗi, tôi đã bao gồm một hàm thay thế khi gọi JSON.opesify để xóa tất cả các hàm, nhưng sau đó nhận ra rằng nó cũng có một số hành vi kỳ lạ:

var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
    console.log(key === ''); // true (?)
    console.log(value === error); // true (?)
});

Nó dường như không lặp lại đối tượng như bình thường, và do đó tôi không thể kiểm tra xem khóa có phải là hàm không và bỏ qua nó.

Câu hỏi

Có cách nào để xâu chuỗi các thông báo Lỗi gốc JSON.stringifykhông? Nếu không, tại sao hành vi này xảy ra?

Phương pháp khắc phục điều này

  • Gắn bó với các thông báo lỗi dựa trên chuỗi đơn giản hoặc tạo các đối tượng lỗi cá nhân và không dựa vào đối tượng Lỗi gốc.
  • Kéo thuộc tính: JSON.stringify({ message: error.message, stack: error.stack })

Cập nhật

@Ray Toal Đề xuất trong một nhận xét rằng tôi hãy xem các mô tả tài sản . Bây giờ rõ ràng tại sao nó không hoạt động:

var error = new Error('simple error message');
var propertyNames = Object.getOwnPropertyNames(error);
var descriptor;
for (var property, i = 0, len = propertyNames.length; i < len; ++i) {
    property = propertyNames[i];
    descriptor = Object.getOwnPropertyDescriptor(error, property);
    console.log(property, descriptor);
}

Đầu ra:

stack { get: [Function],
  set: [Function],
  enumerable: false,
  configurable: true }
arguments { value: undefined,
  writable: true,
  enumerable: false,
  configurable: true }
type { value: undefined,
  writable: true,
  enumerable: false,
  configurable: true }
message { value: 'simple error message',
  writable: true,
  enumerable: false,
  configurable: true }

Phím : enumerable: false.

Câu trả lời được chấp nhận cung cấp một cách giải quyết cho vấn đề này.


3
Bạn đã kiểm tra các mô tả thuộc tính cho các thuộc tính trong đối tượng lỗi chưa?
Ray Toal

3
Câu hỏi cho tôi là "tại sao", và tôi đã tìm thấy câu trả lời nằm ở cuối câu hỏi. Không có gì sai khi đăng câu trả lời cho câu hỏi của riêng bạn và có thể bạn sẽ nhận được nhiều tín nhiệm hơn theo cách đó. :-)
Michael Scheper

Câu trả lời:


178

Bạn có thể xác định một Error.prototype.toJSONđể lấy một đồng bằng Objectđại diện cho Error:

if (!('toJSON' in Error.prototype))
Object.defineProperty(Error.prototype, 'toJSON', {
    value: function () {
        var alt = {};

        Object.getOwnPropertyNames(this).forEach(function (key) {
            alt[key] = this[key];
        }, this);

        return alt;
    },
    configurable: true,
    writable: true
});
var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error));
// {"message":"testing","detail":"foo bar"}

Sử dụng Object.defineProperty()thêm toJSONmà không có nó là một enumerabletài sản chính nó.


Về sửa đổi Error.prototype, trong khi toJSON()có thể không được xác định Errorcụ thể cho s, phương thức vẫn được chuẩn hóa cho các đối tượng nói chung (ref: bước 3). Vì vậy, nguy cơ va chạm hoặc xung đột là tối thiểu.

Mặc dù vậy, vẫn còn tránh nó hoàn toàn, JSON.stringify()của replacertham số có thể được sử dụng thay vì:

function replaceErrors(key, value) {
    if (value instanceof Error) {
        var error = {};

        Object.getOwnPropertyNames(value).forEach(function (key) {
            error[key] = value[key];
        });

        return error;
    }

    return value;
}

var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error, replaceErrors));

3
Nếu bạn sử dụng .getOwnPropertyNames()thay vì .keys(), bạn sẽ có được các thuộc tính không thể đếm được mà không cần phải xác định chúng theo cách thủ công.

8
Tốt hơn là không thêm vào Error.prototype, có thể đưa ra các vấn đề khi trong phiên bản tương lai của JavaScrip, Error.prototype thực sự có hàm toJSON.
Jos de Jong

3
Cẩn thận! Giải pháp này phá vỡ xử lý lỗi trong trình điều khiển mongodb nút gốc: jira.mongodb.org/browse/NODE-554
Sebastian Nowak

5
Trong trường hợp bất cứ ai quan tâm đến mối liên kết lỗi và xung đột đặt tên: nếu sử dụng tùy chọn thay thế, bạn nên chọn một tên tham số khác nhau cho keytrong function replaceErrors(key, value)để tránh đặt tên xung đột với .forEach(function (key) { .. }); các replaceErrors keytham số là không sử dụng trong câu trả lời này.
404 Không tìm thấy

2
Cái bóng keytrong ví dụ này, trong khi được cho phép, có khả năng gây nhầm lẫn vì nó gây nghi ngờ về việc liệu tác giả có ý định đề cập đến biến ngoài hay không. propNamesẽ là một lựa chọn biểu cảm hơn cho vòng lặp bên trong. (BTW, tôi nghĩ @ 404NotFound nghĩa "Linter" (công cụ phân tích tĩnh) không "mối liên kết" ) Trong mọi trường hợp, sử dụng một tùy chỉnh replacerchức năng là một giải pháp tuyệt vời cho điều này vì nó giải quyết vấn đề trong một, vị trí thích hợp và không có nguồn gốc ALTER / hành vi toàn cầu.
iX3

260
JSON.stringify(err, Object.getOwnPropertyNames(err))

dường như làm việc

[ từ một nhận xét của / u / ub3rgeek trên / r / javascript ] và bình luận của felixfbeck bên dưới


56
Kết hợp các câu trả lời,JSON.stringify(err, Object.getOwnPropertyNames(err))
felixfbecker

5
Điều này hoạt động tốt cho một đối tượng Lỗi ExpressJS riêng, nhưng nó sẽ không hoạt động với lỗi Mongoose. Lỗi Mongoose có các đối tượng lồng nhau cho ValidationErrorcác loại. Điều này sẽ không xâu chuỗi errorsđối tượng lồng nhau trong một đối tượng lỗi Mongoose loại ValidationError.
steampowered

4
đây sẽ là câu trả lời, bởi vì đó là cách đơn giản nhất để làm điều này.
Huân

7
@felixfbecker Điều đó chỉ tìm kiếm tên tài sản sâu một cấp . Nếu bạn có var spam = { a: 1, b: { b: 2, b2: 3} };và chạy Object.getOwnPropertyNames(spam), bạn sẽ nhận được ["a", "b"]- lừa đảo ở đây, vì bđối tượng có nó b. Bạn sẽ nhận được cả hai trong cuộc gọi xâu chuỗi của mình, nhưng bạn sẽ bỏ lỡspam.b.b2 . Điều đó thật xấu.
ruffin

1
@ruffin đó là sự thật, nhưng nó thậm chí có thể được mong muốn. Tôi nghĩ những gì OP muốn chỉ là đảm bảo messagestackđược bao gồm trong JSON.
felixfbecker

74

Vì không ai nói về lý do tại sao , tôi sẽ trả lời nó.

Tại sao điều này JSON.stringifytrả về một đối tượng trống?

> JSON.stringify(error);
'{}'

Câu trả lời

Từ tài liệu của JSON.opesify () ,

Đối với tất cả các phiên bản Đối tượng khác (bao gồm Map, Set, WeakMap và Weakset), chỉ các thuộc tính có thể đếm được của chúng sẽ được tuần tự hóa.

Errorđối tượng không có thuộc tính vô số, đó là lý do tại sao nó in một đối tượng trống.


4
Lạ không ai còn bận tâm. Miễn là công việc sửa chữa tôi giả sử :)
Ilya Chernomordik

1
Phần đầu tiên của câu trả lời này là không chính xác. Có một cách để sử JSON.stringifydụng replacertham số của nó .
Todd Chaffee

1
@ToddChaffee đó là một điểm tốt. Tôi đã sửa câu trả lời của mình. Vui lòng kiểm tra nó và cảm thấy tự do để cải thiện nó. Cảm ơn.
Sanghyun Lee

52

Sửa đổi câu trả lời tuyệt vời của Jonathan để tránh vá khỉ:

var stringifyError = function(err, filter, space) {
  var plainObject = {};
  Object.getOwnPropertyNames(err).forEach(function(key) {
    plainObject[key] = err[key];
  });
  return JSON.stringify(plainObject, filter, space);
};

var error = new Error('testing');
error.detail = 'foo bar';

console.log(stringifyError(error, null, '\t'));

3
Lần đầu tiên tôi được nghe monkey patching:)
Chris Prince

2
@ChrisPrince Nhưng đây không phải là lần cuối cùng, đặc biệt là JavaScript! Đây là Wikipedia về Monkey Patching , chỉ để biết thông tin của mọi người trong tương lai. (Trong câu trả lời của Jonathan , như Chris hiểu, bạn đang thêm một chức năng mới toJSON, trực tiếp vào Errornguyên mẫu , thường không phải là một ý tưởng tuyệt vời. Có lẽ ai đó đã có, điều này kiểm tra, nhưng sau đó bạn không biết những gì phiên bản khác cũng vậy. Hoặc nếu ai đó bất ngờ lấy của bạn hoặc giả sử nguyên mẫu của Error có các thuộc tính cụ thể, mọi thứ có thể bị hỏng.)
ruffin

Điều này là tốt, nhưng bỏ qua chồng lỗi (được hiển thị trong bảng điều khiển). không chắc chắn về các chi tiết, nếu đây là liên quan đến Vue hoặc những gì, chỉ muốn đề cập đến điều này.
phil294

22

Có một gói Node.js tuyệt vời cho điều đó : serialize-error.

Nó xử lý tốt các đối tượng Error thậm chí lồng nhau, điều mà tôi thực sự cần nhiều trong dự án của mình.

https://www.npmjs.com/package/serialize-error


Không, nhưng nó có thể được phiên mã để làm như vậy. Xem bình luận này .
iX3

Đây là câu trả lời đúng. Lỗi nối tiếp không phải là một vấn đề nhỏ và tác giả của thư viện (một nhà phát triển xuất sắc với nhiều gói rất phổ biến) đã tìm đến các độ dài đáng kể để xử lý các trường hợp cạnh, như có thể thấy trong README: "Thuộc tính tùy chỉnh được giữ nguyên. các thuộc tính được giữ không thể đếm được (tên, tin nhắn, ngăn xếp). Các thuộc tính có thể được liệt kê (tất cả các thuộc tính bên cạnh các thuộc tính không đếm được). Các tham chiếu tròn được xử lý. "
Dan Dascalescu

9

Bạn cũng có thể xác định lại các thuộc tính không thể đếm được thành vô số.

Object.defineProperty(Error.prototype, 'message', {
    configurable: true,
    enumerable: true
});

và có thể stacktài sản quá.


9
Đừng thay đổi các đối tượng bạn không sở hữu, nó có thể phá vỡ các phần khác trong ứng dụng của bạn và chúc may mắn tìm ra lý do tại sao.
Fregante 17/03/19

7

Chúng ta cần tuần tự hóa một hệ thống phân cấp đối tượng tùy ý, trong đó gốc hoặc bất kỳ thuộc tính lồng nhau nào trong cấu trúc phân cấp có thể là các trường hợp Lỗi.

Giải pháp của chúng tôi là sử dụng replacerparam của JSON.stringify(), ví dụ:

function jsonFriendlyErrorReplacer(key, value) {
  if (value instanceof Error) {
    return {
      // Pull all enumerable properties, supporting properties on custom Errors
      ...value,
      // Explicitly pull Error's non-enumerable properties
      name: value.name,
      message: value.message,
      stack: value.stack,
    }
  }

  return value
}

let obj = {
    error: new Error('nested error message')
}

console.log('Result WITHOUT custom replacer:', JSON.stringify(obj))
console.log('Result WITH custom replacer:', JSON.stringify(obj, jsonFriendlyErrorReplacer))


5

Không có câu trả lời nào ở trên dường như tuần tự hóa các thuộc tính đúng trên nguyên mẫu Lỗi (vì getOwnPropertyNames()không bao gồm các thuộc tính được kế thừa). Tôi cũng không thể xác định lại các thuộc tính như một trong những câu trả lời được đề xuất.

Đây là giải pháp tôi đã đưa ra - nó sử dụng lodash nhưng bạn có thể thay thế lodash bằng các phiên bản chung của các chức năng đó.

 function recursivePropertyFinder(obj){
    if( obj === Object.prototype){
        return {};
    }else{
        return _.reduce(Object.getOwnPropertyNames(obj), 
            function copy(result, value, key) {
                if( !_.isFunction(obj[value])){
                    if( _.isObject(obj[value])){
                        result[value] = recursivePropertyFinder(obj[value]);
                    }else{
                        result[value] = obj[value];
                    }
                }
                return result;
            }, recursivePropertyFinder(Object.getPrototypeOf(obj)));
    }
}


Error.prototype.toJSON = function(){
    return recursivePropertyFinder(this);
}

Đây là thử nghiệm tôi đã làm trong Chrome:

var myError = Error('hello');
myError.causedBy = Error('error2');
myError.causedBy.causedBy = Error('error3');
myError.causedBy.causedBy.displayed = true;
JSON.stringify(myError);

{"name":"Error","message":"hello","stack":"Error: hello\n    at <anonymous>:66:15","causedBy":{"name":"Error","message":"error2","stack":"Error: error2\n    at <anonymous>:67:20","causedBy":{"name":"Error","message":"error3","stack":"Error: error3\n    at <anonymous>:68:29","displayed":true}}}  

2

Tôi đã làm việc trên một định dạng JSON cho các ứng dụng đăng nhập và kết thúc ở đây để cố gắng giải quyết một vấn đề tương tự. Sau một thời gian, tôi nhận ra rằng tôi có thể khiến Node thực hiện công việc:

const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
    if (value instanceof Error) {
        return util.format(value);
    } else {
        return value;
    }
}

1
Nó nên instanceofvà không instanceOf.
lakshman.pasala
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.