Bir Hata JSON.stringify kullanarak dizgi yapmak mümkün değil mi?


330

Sorunu yeniden oluşturma

Web soketlerini kullanarak hata mesajlarını iletmeye çalışırken bir sorunla karşılaşıyorum. Daha JSON.stringifygeniş bir kitleye hitap etmek için kullandığım sorunu çoğaltabilirim :

// 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);
    '{}'

Sorun şu ki sonunda boş bir nesne ile karşılaşıyorum.

Ne denedim

Tarayıcılar

İlk olarak node.js'den ayrılmayı ve çeşitli tarayıcılarda çalıştırmayı denedim. Chrome 28 sürümü bana aynı sonucu veriyor ve ilginç bir şekilde Firefox en azından bir girişimde bulunuyor, ancak mesajı bıraktı:

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

İkame işlevi

Sonra Error.prototype baktım . Prototipin toString ve toSource gibi yöntemler içerdiğini gösterir . İşlevlerin dizilemediğini bilerek, tüm işlevleri kaldırmak için JSON.stringify'ı çağırırken bir replacer işlevi ekledim, ancak daha sonra bazı garip davranışlar olduğunu fark ettim:

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

Nesnenin üzerinde normalde olduğu gibi dönmüyor gibi görünüyor ve bu nedenle anahtarın bir işlev olup olmadığını kontrol edip görmezden gelemiyorum.

Soru

Yerel Hata iletilerini dize etmenin bir yolu var mı JSON.stringify? Değilse, bu davranış neden oluşur?

Bunu aşma yöntemleri

  • Basit dize tabanlı hata mesajlarına sadık kalın veya kişisel hata nesneleri oluşturun ve yerel Hata nesnesine güvenmeyin.
  • Çekme özellikleri: JSON.stringify({ message: error.message, stack: error.stack })

Güncellemeler

@Ray Toal Bir yorumda özellik tanımlayıcılarına bir göz atmamı önerdi . Şimdi neden işe yaramadığı açık:

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

Çıktı:

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 }

Anahtar: enumerable: false.

Kabul edilen cevap, bu sorun için bir geçici çözüm sağlar.


3
Hata nesnesindeki özellikler için özellik tanımlayıcılarını incelediniz mi?
Ray Toal

3
Benim için soru 'neden' idi ve cevabın sorunun altında olduğunu buldum. Kendi sorunuza yanıt göndermenin yanlış bir yanı yok ve muhtemelen bu şekilde daha fazla kredi alacaksınız. :-)
Michael Scheper

Yanıtlar:


178

Bir tanımlayabilirsiniz Error.prototype.toJSONbölümü düz almak için Objecttemsil 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"}

Kullanma özelliği bir özellik olmadan Object.defineProperty()eklenir .toJSONenumerable


Değişiklikle ilgili olarak Error.prototype, spesifik olarak s toJSON()için tanımlanmasa da , yöntem hala genel olarak nesneler için standartlaştırılmıştır (ref: adım 3). Bu nedenle, çarpışma veya çatışma riski asgari düzeydedir.Error

Hala tam olarak bunu önlemek için de,, JSON.stringify()'nin replacerparametre yerine kullanılabilir:

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
Eğer kullanırsanız .getOwnPropertyNames()yerine .keys(), onları el tanımlamak zorunda kalmadan olmayan enumerable özelliklerini alırsınız.

8
Error.prototype daha iyi eklememek, JavaScripti gelecekteki bir sürümünde Error.prototype gerçekten bir toJSON işlevi olduğunda sorunları verebilir.
Jos de Jong

3
Dikkatli! Bu çözüm, yerel düğüm mongodb sürücüsünde hata işlemeyi sonlandırıyor: jira.mongodb.org/browse/NODE-554
Sebastian Nowak

5
Herhangi birinin bağlayıcı hatalarına ve adlandırma çakışmalarına dikkat etmesi durumunda: replacer seçeneğini kullanıyorsanız, adlandırma çakışmasını önlemek için keyin function replaceErrors(key, value)için farklı bir parametre adı seçmelisiniz .forEach(function (key) { .. }); replaceErrors keyParametre Bu yanıt kullanılmayan olduğunu.
404 Bulunamadı

2
keyBu örnekte gölgelenmesine izin verilirken, yazarın dış değişkene atıfta bulunup bulunmayacağından şüphe duyduğundan potansiyel olarak kafa karıştırıcıdır. propNameiç döngü için daha etkileyici bir seçim olacaktır. (BTW, 404NotFound demek @ düşünüyorum "linter" (statik analiz aracı) değil "bağlayıcı" özel kullanarak, Her durumda) replacero birinde problem, uygun yer çözer ve alter yerli değil yaptığı gibi bunun için mükemmel bir çözüm işlevi olan / global davranış.
iX3

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

çalışıyor gibi görünüyor

[ / r / javascript üzerindeki / u / ub3rgeek tarafından yapılan bir yorumdan ] ve felixfbecker'in aşağıdaki yorumu


57
Cevapları JSON.stringify(err, Object.getOwnPropertyNames(err))
tararken

5
Bu, yerel bir ExpressJS Error nesnesi için iyi çalışır, ancak bir Mongoose hatasıyla çalışmaz. Firavun faresi hataları ValidationErrortürler için iç içe nesneler içerir . Bu errors, türdeki bir Mongoose hata nesnesindeki yuvalanmış nesneyi dize etmez ValidationError.
steampowered

4
cevap bu olmalı, çünkü bunu yapmanın en basit yolu bu.
Huan

7
@felixfbecker Bu sadece bir seviye derinlikteki mülk adlarını arar . Eğer varsa var spam = { a: 1, b: { b: 2, b2: 3} };ve koşarsanız Object.getOwnPropertyNames(spam), ["a", "b"]burada aldatıcı olursunuz , çünkü bnesnenin kendi vardır b. Eğer stringify çağrısında hem olsun istiyorum, ama sen özlerdinizspam.b.b2 . Bu kötü.
17'de

1
@ruffin bu doğru, ama arzu bile olabilir. Ne OP istediğini emin olmak için biraz olduğunu düşünüyorum messageve stackJSON dahildir.
felixfbecker

74

Kimse neden bölümden bahsetmediği için ben cevaplayacağım.

Bu neden JSON.stringifyboş bir nesne döndürüyor?

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

Cevap

JSON.stringify () belgesinden ,

Diğer tüm Object örnekleri için (Harita, Küme, WeakMap ve WeakSet dahil), yalnızca bunların numaralandırılabilir özellikleri serileştirilir.

ve Errornesnenin numaralandırılabilir özellikleri yoktur, bu yüzden boş bir nesne yazdırır.


4
Garip kimse rahatsız bile etmedi. Düzeltme çalışmaları varsayalım :)
Ilya Chernomordik

1
Bu cevabın ilk kısmı doğru değil. Parametresini JSON.stringifykullanarak kullanmanın bir yolu vardır replacer.
Todd Chaffee

1
@ToddChaffee bu iyi bir nokta. Cevabımı düzelttim. Lütfen kontrol edin ve geliştirmek için çekinmeyin. Teşekkürler.
Sanghyun Lee

52

Maymun yamasını önlemek için Jonathan'ın harika cevabını değiştirmek:

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
İlk kez duydum monkey patching:)
Chris Prince

2
@ChrisPrince Ama son kez olmayacak, esp JavaScript! İşte Monkey Patching hakkındaki Wikipedia , sadece gelecekteki kişilerin bilgileri için. ( Jonathan'ın cevabında , Chris'in anladığı gibi toJSON, doğrudan Errorprototipine yeni bir işlev ekliyorsunuz , bu genellikle iyi bir fikir değil. Belki de başka birinin zaten sahip olduğu, kontrol ettiği, ancak sonra ne olduğunu bilmiyorsunuz) Veya biri beklenmedik bir şekilde sizinkini alırsa veya Hata prototipinin belirli özelliklere sahip olduğunu varsayarsa, işler
boşalabilir

bu güzel, ancak hata yığınını (konsolda gösterilen) atlar. ayrıntılardan emin değilim, eğer bu Vue ile ilgili veya ne ise, sadece bundan bahsetmek istedim.
phil294

23

Bunun için harika node.js paket var: serialize-error.

Projemde aslında çok ihtiyacım olan iç içe Hata nesnelerini bile işler.

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


Hayır, ancak bunu yapmak aktarılabilir. Bu yoruma bakın .
iX3

Bu doğru cevap. Hataları serileştirmek önemsiz bir sorun değildir ve kütüphanenin yazarı (birçok popüler pakete sahip mükemmel bir geliştirici), README'de görüldüğü gibi uç vakaları ele almak için önemli uzunluklara gitti: "Özel özellikler korunuyor. Numaralandırılamaz özellikleri numaralandırılamaz olarak tutulur (ad, ileti, yığın) Numaralandırılabilir özellikler numaralandırılabilir olarak tutulur (numaralandırılamayanların dışındaki tüm özellikler). Dairesel başvurular işlenir. "
Dan Dascalescu

9

Ayrıca, numaralandırılamayan bu özellikleri numaralandırılabilir olarak yeniden tanımlayabilirsiniz.

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

ve belki de stackmülkiyet.


9
Sahip olmadığınız nesneleri değiştirmeyin , uygulamanızın diğer bölümlerini kırabilir ve nedenini bulabilir.
fregante

7

Kökün veya hiyerarşideki yuvalanmış özelliklerin herhangi birinin Hata örnekleri olabileceği rasgele bir nesne hiyerarşisini serileştirmemiz gerekiyordu.

Çözümümüz kullanmaktı replacerarasında param JSON.stringify()örneğin:

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

Yukarıdaki yanıtların hiçbiri, Hata prototipinde bulunan özellikleri düzgün bir şekilde serileştirmemiştir ( getOwnPropertyNames()devralınan özellikleri içermediğinden). Ayrıca önerilen cevaplardan biri gibi özellikleri yeniden tanımlayamadım.

Bu geldiğim çözüm - lodash kullanıyor ancak lodash'ı bu işlevlerin genel sürümleriyle değiştirebilirsiniz.

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

İşte Chrome'da yaptığım test:

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

Günlük ekleyicileri için bir JSON biçimi üzerinde çalışıyordum ve burada benzer bir sorunu çözmeye çalışıyordum. Bir süre sonra Düğüm'ün işi yapmasını sağlayabildiğimi fark ettim:

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

1
Olmalı instanceofve olmamalı instanceOf.
lakshman.pasala
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.