Подтвердить что ты не робот

Почему JSON.stringify не сериализует значения прототипа?

Я недавно работал с большим количеством разборов JSON и передачей Javascript в Node.js и браузерах и столкнулся с этой загадкой.

Любые объекты, которые я создал с помощью конструктора, не могут быть полностью полностью сериализованы через JSON.stringify, ЕСЛИ Я не инициализировал все значения внутри конструктора индивидуально! Это означает, что мой прототип становится практически бесполезным при разработке этих классов.

Может кто-нибудь пролить свет на то, почему следующее не сериализуется, как я ожидаю?

var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a);

Что происходит:

a_string == {"initialisedValue": "Вы меня видите!" }

Я ожидаю:

a_string == {"initialisedValue": "Вы можете видеть меня!", "uninitialisedValue": "Вы не можете видеть меня!" }


Обновление (01-10-2019):

Наконец заметил @ncardeli Answer, который позволяет нам сделать что-то вроде следующего, чтобы выполнить мои требования (в 2019 году!):

замещать var a_string = JSON.stringify(a);

с участием var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));

Полный код:

var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));

console.log(a_string)
4b9b3361

Ответ 1

Просто потому, что это работает JSON. Из спецификация ES5:

Пусть K - внутренний список строк, состоящий из имен всех собственных свойств значения, чей атрибут [[Enumerable]] имеет значение true.

Это имеет смысл, потому что в спецификации JSON нет механизма для сохранения информации, которая потребуется для синтаксического анализа строки JSON обратно в объект JavaScript, если бы были включены унаследованные свойства. В вашем примере, как бы это анализировалось:

{ "initializedValue": "Вы можете видеть меня!", "uninitializedValue": "Вы меня не видите!" }

Нет никакой информации, чтобы разобрать ее в чем-либо, кроме плоского объекта, с двумя парами ключ-значение.

И если вы думаете об этом, JSON не предназначен для прямой привязки к объектам JavaScript. Другие языки должны иметь возможность анализировать строки JSON в простые структуры пар имя-значение. Если строки JSON содержат всю информацию, необходимую для сериализации полных цепей видимости JavaScript, другие языки могут быть менее способны разбирать это во что-то полезное. По словам Дугласа Крокфорда на json.org:

Эти [хэш-таблицы и массивы] являются универсальными структурами данных. Практически все современные языки программирования поддерживают их в той или иной форме. Имеет смысл, что формат данных, который является взаимозаменяемым с языками программирования, также основывается на этих структурах.

Ответ 2

Я хотел бы добавить, что даже если JSON.stringify будет только строчить собственные свойства объекта, как объяснено в принятом ответе, вы можете изменить поведение процесса строковой привязки, указав массив String как второй параметр JSON.stringify (называемый массивом replacer).

Если вы укажете массив из String со списком свойств whitelist свойств, алгоритм стробирования изменит его поведение и рассмотрит свойства в цепочке прототипов.

От спецификация ES5:

  1. Если PropertyList не undefined, то

    а. Пусть K - свойствоList.

  2. Else

    а. Пусть K - внутренний список строк, состоящий из имен все собственные свойства значения, атрибут [[Enumerable]] правда. Порядок строк должен быть таким же, как и при использовании стандартная встроенная функция Object.keys.

Если вы знаете имя свойства объекта, которое должно быть увязано заранее, вы можете сделать что-то вроде этого:

var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
//  a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }

Ответ 3

Я приземлился на этой странице при использовании мангуста: если другие пользователи имеют схожие проблемы, может оказаться полезным следующий вопрос (и связанные с ним ответы):

Как вы превращаете документ Mongoose в простой объект?

(в основном: используйте метод Model toObject())

Ответ 4

Существует еще одна возможность иметь свойства из прототипа для строкового преобразования.

Начиная со спецификации JSON (15.12.3 stringify http://es5.github.io/#x15.12.3):

[...]
2. If Type(value) is Object, then
    a. Let toJSON be the result of calling the [[Get]] internal method of value with argument "toJSON".
    b. If IsCallable(toJSON) is true
        i. Let value be the result of calling the [[Call]] internal method of toJSON passing value as the this value and with an argument list consisting of key.
[...]

Так что вы можете написать свою собственную функцию JSON stringifier. Общая реализация может быть:

class Cat {
    constructor(age) {
        this.age = age;
    }

    get callSound() {
        // This does not work as this getter-property is not enumerable
        return "Meow";
    }

    toJSON() {
        const jsonObj = {}
        const self = this; // If you can use arrow functions just use 'this'-keyword.

        // Object.keys will list all 'enumerable' properties
        // First we look at all own properties of 'this'
        Object.keys(this).forEach(function(k) {
            jsonObj[k] = self[k];
        });
        // Then we look at all own properties of this 'prototype'
        Object.keys(Object.getPrototypeOf(this)).forEach(function(k) {
            jsonObj[k] = self[k];
        });

        return JSON.stringify(jsonObj);
    }
}

Object.defineProperty(Cat.prototype, 'callSound2', {
    // The 'enumerable: true' is important!
    value: "MeowMeow", enumerable: true
});


let aCat = new Cat(4);

console.log(JSON.stringify(aCat));
// prints "{\"age\":4,\"callSound2\":\"MeowMeow\"}"

Это работает до тех пор, пока правильные свойства (те, которые вы на самом деле хотите, чтобы они были строковыми в JSON) были перечисляемыми, а те, которые вы не хотите, чтобы они были строковыми, - нет. Поэтому вам нужно быть осторожным, какие свойства вы делаете перечисляемыми или становитесь неявно перечислимыми, когда вы присваиваете значения this.

Другая возможность состоит в том, чтобы присваивать каждое свойство, которое вы на самом деле хотите, чтобы строки были по очереди вручную. Который может быть менее подвержен ошибкам.