在 JavaScript 中检查对象是否可序列化的可靠方法

Reliable way to check if objects is serializable in JavaScript

是否有已知的方法或库已经有帮助程序来评估对象是否可序列化 JavaScript?

我尝试了以下方法,但它没有涵盖原型属性,因此它提供了误报:

_.isEqual(obj, JSON.parse(JSON.stringify(obj))

还有另一个 lodash 函数可能会让我更接近真相,_.isPlainObject。但是,while_.isPlainObject(new MyClass())returnsfalse_.isPlainObject({x: new MyClass()})returnstrue,所以需要递归应用

在我自己冒险之前,有没有人知道一种已经可靠的方法来检查 JSON.parse(JSON.stringify(obj)) 是否真的会产生与 obj 相同的对象?

function isSerializable(obj) {
  var isNestedSerializable;
  function isPlain(val) {
    return (typeof val === 'undefined' || typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number' || Array.isArray(val) || _.isPlainObject(val));
  }
  if (!isPlain(obj)) {
    return false;
  }
  for (var property in obj) {
    if (obj.hasOwnProperty(property)) {
      if (!isPlain(obj[property])) {
        return false;
      }
      if (typeof obj[property] == "object") {
        isNestedSerializable = isSerializable(obj[property]);
        if (!isNestedSerializable) {
          return false;
        }
      }
    }
  }
  return true;
}

递归迭代所有给定的对象属性。它们可以是:

  • 普通对象由对象构造函数创建的对象或 [[Prototype]] 为 null 的对象。" -来自 lodash 文档)
  • 数组
  • 字符串数字布尔值
  • 未定义

传递的 obj 中的任何其他值将导致它被理解为 "un-serializable"。

(老实说,我不是绝对肯定我没有忽略某些 serializable/non-serializable 数据类型的检查,实际上我认为这取决于 "serializable" 的定义 - 任何评论和欢迎提出建议。)

最后,我创建了自己的方法来利用 Underscore/Lodash 的 _.isPlainObject。我的功能最终与@bardzusny 提议的类似,但我也发布了我的功能,因为我更喜欢 simplicity/clarity。随意概述 pros/cons.

var _ = require('lodash');

exports.isSerializable = function(obj) {
  if (_.isUndefined(obj) ||
      _.isNull(obj) ||
      _.isBoolean(obj) ||
      _.isNumber(obj) ||
      _.isString(obj)) {
    return true;
  }

  if (!_.isPlainObject(obj) &&
      !_.isArray(obj)) {
    return false;
  }

  for (var key in obj) {
    if (!exports.isSerializable(obj[key])) {
      return false;
    }
  }

  return true;
};

这是@treznik 解决方案的稍微多一点的 Lodashy ES6 版本

    export function isSerialisable(obj) {

        const nestedSerialisable = ob => (_.isPlainObject(ob) || _.isArray(ob))  &&
                                         _.every(ob, isSerialisable);

        return  _.overSome([
                            _.isUndefined,
                            _.isNull,
                            _.isBoolean,
                            _.isNumber,
                            _.isString,
                            nestedSerialisable
                        ])(obj)
    }

测试

    describe.only('isSerialisable', () => {

        it('string', () => {
            chk(isSerialisable('HI'));
        });

        it('number', () => {
            chk(isSerialisable(23454))
        });

        it('null', () => {
            chk(isSerialisable(null))
        });

        it('undefined', () => {
            chk(isSerialisable(undefined))
        });


        it('plain obj', () => {
            chk(isSerialisable({p: 1, p2: 'hi'}))
        });

        it('plain obj with func', () => {
            chkFalse(isSerialisable({p: 1, p2: () => {}}))
        });


        it('nested obj with func', () => {
            chkFalse(isSerialisable({p: 1, p2: 'hi', n: { nn: { nnn: 1, nnm: () => {}}}}))
        });

        it('array', () => {
            chk(isSerialisable([1, 2, 3, 5]))
        });

        it('array with func', () => {
            chkFalse(isSerialisable([1, 2, 3, () => false]))
        });

        it('array with nested obj', () => {
            chk(isSerialisable([1, 2, 3, { nn: { nnn: 1, nnm: 'Hi'}}]))
        });

        it('array with newsted obj with func', () => {
            chkFalse(isSerialisable([1, 2, 3, { nn: { nnn: 1, nnm: () => {}}}]))
        });

    });

}

下面是如何在不依赖第 3 方库的情况下实现这一点。

我们通常会想到使用 typeof 运算符来完成这种任务,但它本身是不可信任的,否则我们最终会得到这样的废话:

typeof null === "object" // true
typeof NaN === "number" // true

所以我们需要做的第一件事就是找到一种方法来可靠地检测任何值的类型(摘自MDN Docs):

const getTypeOf = (value: unknown) => {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
};

然后我们可以递归遍历对象或数组(如果有的话)并在每一步检查反序列化输出是否与输入类型匹配:

const SERIALIZATION_ERROR = new Error(
  `the input value could not be serialized`
);

const serialize = (input: unknown) => {
  try {
    const serialized = JSON.stringify(input);
    const inputType = getTypeOf(input);

    const deserialized = JSON.parse(serialized);
    const outputType = getTypeOf(parsed);

    if (outputType !== inputType) throw SERIALIZATION_ERROR;

    if (inputType === "object") {
      Object.values(input as Record<string, unknown>).forEach((value) =>
        serialize(value)
      );
    }

    if (inputType === "array") {
      (input as unknown[]).forEach((value) => serialize(value));
    }

    return serialized;
  } catch {
    throw SERIALIZATION_ERROR;
  }
};