【问题标题】:JSON.parse and JSON.stringify are not idempotent and that is badJSON.parse 和 JSON.stringify 不是幂等的,这很糟糕
【发布时间】:2015-08-18 05:10:33
【问题描述】:

这个问题是多部分的-

(1a) JSON 是 JavaScript 的基础,那为什么没有 JSON 类型呢? JSON 类型将是格式化为 JSON 的字符串。在数据被更改之前,它将被标记为已解析/字符串化。一旦数据被更改,它就不会被标记为 JSON,并且需要重新解析/重新字符串化。

(1b) 在某些软件系统中,是否可能(意外地)尝试通过网络发送纯 JS 对象而不是序列化 JS 对象?为什么不尝试避免这种情况呢?

(1c) 为什么我们不能在不先对 JavaScript 对象进行字符串化的情况下直接调用 JSON.parse

    var json = {   //JS object in properJSON format
        "baz":{
            "1":1,
            "2":true,
            "3":{}
        }
    };

    var json0 = JSON.parse(json); //will throw a parse error...bad...it should not throw an error if json var is actually proper JSON.

所以我们别无选择,只能这样做:

 var json0= JSON.parse(JSON.stringify(json));

但是,也有一些不一致的地方,例如:

JSON.parse(true); //works
JSON.parse(null); //works
JSON.parse({}); //throws error

(2) 如果我们一直在同一个对象上调用JSON.parse,最终它会抛出一个错误。例如:

var json = {   //same object as above
    "baz":{
        "1":1,
        "2":true,
        "3":{}
    }
};

var json1 = JSON.parse(JSON.stringify(json));
var json2 = JSON.parse(json1); //throws an error...why

(3) 为什么JSON.stringify 会在输入中无限添加越来越多的斜线?不仅很难读取调试结果,而且实际上会使您处于危险状态,因为一次 JSON.parse 调用不会给您返回一个普通的 JS 对象,您必须多次调用 JSON.parse 才能取回普通的 JS 对象。这很糟糕,意味着在给定的 JS 对象上多次调用 JSON.stringify 是非常危险的。

   var json = {
        "baz":{
            "1":1,
            "2":true,
            "3":{}
        }
    };

    var json2 = JSON.stringify(json);
    console.log(json2);

    var json3 = JSON.stringify(json2);
    console.log(json3);

    var json4 = JSON.stringify(json3);
    console.log(json4);

    var json5 = JSON.stringify(json4);
    console.log(json5); 

(4) 我们应该能够在不改变结果的情况下一遍又一遍地调用的函数的名称是什么(IMO JSON.parseJSON.stringify 应该如何表现)?正如您在 cmets 中看到的那样,最好的术语似乎是“幂等”。

(5) 考虑到 JSON 是一种可用于网络对象的序列化格式,你不能调用 JSON.parseJSON.stringify 两次甚至一次,这似乎是完全疯狂的情况下不会产生一些问题。为什么会这样?

如果您正在为 Java、JavaScript 或任何语言发明下一个序列化格式,请考虑这个问题。

IMO 给定对象应该有两种状态。序列化状态和反序列化状态。在具有更强类型系统的软件语言中,这通常不是问题。但是对于 JavaScript 中的 JSON,如果在同一个对象上调用 JSON.parse 两次,我们会遇到致命的异常。同样,如果我们在同一个对象上调用 JSON.stringify 两次,我们可能会进入不可恢复的状态。就像我说的应该有两种状态,只有两种状态,普通 JS 对象和序列化 JS 对象。

【问题讨论】:

  • parse 从一个有效的 JSON 字符串返回一个有效的 JavaScript 对象,stringify 从一个 JavaScript 对象返回一个有效的 JSON 字符串
  • 你的第一个场景,你没有 JSON,你有一个 object
  • 1) 因为 JSON.parse 需要一个 JSON 字符串,而您正在为它提供一个 Javascript 对象,2) 同样的问题。 JSON.parse 将其转换为一个对象,并且您正在提供另一个 javascript 对象。
  • 想想 javascript 中的 splitjoinsplit 将字符串转换为数组,而join 将数组转换为字符串。你不能 join join 的输出,因为它不是正确的类型。
  • (4) 你要找的词是幂等的。 en.wikipedia.org/wiki/Idempotence

标签: json node.js


【解决方案1】:

1) JSON.parse 需要一个字符串,你给它一个 Javascript 对象。

2) 与第一个类似的问题。您将字符串提供给需要对象的函数。

3) Stringfy 实际上需要一个字符串,但你给它的是一个 String 对象。因此,它采用与第一个字符串相同的措施来转义引号和斜杠。以便语言可以理解字符串中的引号、其他特殊字符。

4) 您可以为此编写自己的函数。

5) 因为您试图进行非法转换。这与第一个和第二个问题有关。只要提供了正确的对象类型,您就可以根据需要多次调用它。唯一的问题是多余的斜线,但它实际上是标准的。

【讨论】:

  • 在我看来,绝对没有充分的理由说明为什么要使用 #1。 JSON.parse 应该能够解析格式正确为 JSON 的 JS 对象,没有 if ands 或 buts。完全是废话。
  • @AlexMills 虽然我同意你的观点,但它是高度结构化的,并且具有已实施的故障屏蔽方法。确实可以做一些更全局的事情,但这会更容错,因此更少的错误证明。
  • @AlexMills,JSON.parse 是一个需要字符串的函数,因为 JSON 是 javascript 对象的字符串表示形式。名称“JSON.parse”告诉我这个函数将解析 JSON,仅此而已。如果我用其他东西调用 JSON.parse 然后 JSON 作为参数,我会期待一个异常或类似的,而不仅仅是一个无声的“ok” - 这种行为可能会导致奇怪的错误
  • 我写了一些代码来解决这个问题。这种事情应该没有必要,但我不知道更好的方法。
  • @AlexMills 您的断言似乎被误导了。没有理由JSON.parse 应该接受除有效 JSON 字符串之外的任何内容。 #1 中的 JS 对象文字不是 JSON 字符串,因此它不起作用。
【解决方案2】:

我们将从您创作的噩梦开始:字符串输入和整数输出。
IJSON.parse(IJSON.stringify("5")); //=> 5

内置的 JSON 函数不会以这种方式让我们失望:string 输入和 string 输出。
JSON.parse(JSON.stringify("5")); //=> "5"

JSON 必须保留您的原始数据类型

JSON.stringify 视为将数据包装在一个盒子中的函数,将JSON.parse 视为将数据从盒子中取出的函数。

考虑以下几点:

var a = JSON.stringify;
var b = JSON.parse;

var data = "whatever";

b(a(data)) === data; // true

b(b(a(a(data)))) === data; // true

b(b(b(a(a(a(data)))))) === data; // true

也就是说,如果我们把数据放在 3 个盒子里,我们就得把它从 3 个盒子里拿出来。对吧?

如果我将数据放入 2 个框中并从 1 个中取出,我还没有持有我的数据,我持有的是一个包含我的数据的框。对吧?

b(a(a(data))) === data; // false

对我来说似乎很理智......


  1. JSON.parse 将您的数据拆箱。如果没有装箱,则无法拆箱。 JSON.parse 需要一个字符串输入并且你给它一个 JavaScript 对象字面量

  2. JSON.parse 的第一次有效调用将返回一个对象。在此对象输出上再次调用 JSON.parse 将导致与 #1 相同的失败

  3. 重复调用JSON.stringify 将多次“装箱”我们的数据。因此,您当然必须重复调用JSON.parse,然后才能将数据从每个“盒子”中取出

  4. Idempotence

  5. 不,这是完全理智的。您不能在双重邮票上加三重邮票。

    你不会犯这样的错误吧?

    var json = IJSON.stringify("hi");
    IJSON.parse(json);
    //=> "hi"
    

    好的,这是幂等的,但是呢

    var json = IJSON.stringify("5");
    IJSON.parse(json);
    //=> 5
    

    呃哦!我们每次都给它一个字符串,但第二个示例返回一个整数。输入数据类型已丢失!

    JSON 函数会不会在这里让我们失望?

    var json = JSON.stringify("hi");
    JSON.parse(json);
    //=> "hi"
    

    一切都好。那么"5" 呢?

    var json = JSON.stringify("5");
    JSON.parse(json));
    //=> "5"
    

    是的,类型已被保留!JSON 有效,IJSON 无效。


也许是一个更真实的例子:

好的,所以你有一个繁忙的应用程序,有很多开发人员在开发它。它使 对基础数据类型的鲁莽假设。假设它是一个聊天应用程序,当消息从一个点移动到另一个点时,它会对消息进行多次转换。

一路走来:

  1. IJSON.stringify
  2. 数据在网络中移动
  3. IJSON.parse
  4. 另一个IJSON.parse 因为谁在乎?它是幂等的,对吗?
  5. String.prototype.toUpperCase — 因为这是一种格式选择

让我们看看消息

bob: 'hi'
// 1) '"hi"', 2) <network>, 3) "hi", 4) "hi", 5) "HI"

Bob 的消息看起来不错。让我们看看爱丽丝的。

alice: '5'
// 1) '5'
// 2) <network>
// 3) 5
// 4) 5
// 5) Uncaught TypeError: message.toUpperCase is not a function

哦不!服务器刚刚崩溃。你会注意到这里甚至不是重复调用IJSON.parse 失败。即使你调用一次它也会失败。

似乎你从一开始就注定要失败......该死的鲁莽的开发人员和他们粗心的数据处理!

如果 Alice 使用任何碰巧也是有效 JSON 的输入,它将失败

alice: '{"lol":"pwnd"}'
// 1) '{"lol":"pwnd"}'
// 2) <network>
// 3) {lol:"pwnd"}
// 4) {lol:"pwnd"}
// 5) Uncaught TypeError: message.toUpperCase is not a function

好吧,也许是不公平的例子,对吧?你在想,“我没那么鲁莽,我 不会在这样的用户输入上调用IJSON.stringifyIJSON.parse!” 没关系。您从根本上破坏了 JSON,因为原始 无法再提取类型。

如果我使用IJSON 将一个字符串装箱,然后将其拆箱,谁知道我会得到什么?当然不是你,当然也不是使用你鲁莽功能的开发者。

  • “我会得到一个字符串类型吗?”
  • “我会得到一个整数吗?”
  • “也许我会得到一个对象?”
  • “也许我会得到蛋糕。我希望是蛋糕”

无法判断!

您处于一个全新的痛苦世界,因为您从一开始就对数据类型不小心。您的类型很重要,因此请谨慎处理。

JSON.stringify 需要一个对象类型JSON.parse 需要一个字符串类型

现在看到光了吗?

【讨论】:

    【解决方案3】:

    我将尝试为您提供一个原因,为什么不能在同一数据上多次调用 JSON.parse 而不会遇到问题。

    您可能不知道,但 JSON 文档确实不是必须是一个对象。

    这是一个有效的 JSON 文档:

    "some text"
    

    让我们将此文档的表示形式存储在 javascript 变量中:

    var JSONDocumentAsString = '"some text"';
    

    并继续努力:

    var JSONdocument = JSON.parse(JSONDocumentAsString);
    JSONdocument === 'some text';
    

    这将导致错误,因为此字符串不是 JSON 文档的表示形式

    JSON.parse(JSONdocument);
    // SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
    

    在这种情况下,JSON.parse 怎么会猜到 JSONdocument(作为一个字符串)是一个 JSON 文档并且应该原封不动地返回它?

    【讨论】:

    • 没错,这就是为什么我们需要比当前 JSON 标准更好的东西。有类型的东西,我们知道我们得到的东西已经序列化,或者已经反序列化。您在示例中使用 JSON,但想象一下是否有更好的方法。在序列化的情况下,您将不得不用一些字符进行标记。所以“一些文本”不应该是一个有效的 JSON 文档,它应该是 '{json:"some text"}' 或其他。
    • 使用字符串作为有效的 JSON 是个坏主意。如果您在字符串上调用 JSON.stringify,那么您真的不知道原始参数是否代表有效数据。例如,您可以发送 JSON.stringify('garbage\n\n}{"haveFunParsingthis;;;;;');。IMO,JSON.stringify 不应该接受字符串,因为字符串本身不应该是有效的 JSON,AFAICT。
    • 您可能认为使用字符串作为有效的 JSON 是一个坏主意,并且字符串不应该是有效的 JSON,但它们是有效的,而且这不太可能改变。 JSON.parse 和 JSON.stringify 正在使用 JSON,而不是您认为的 JSON 应该是什么。 JSON 可能不会改变,但非常欢迎您想象更好的数据格式
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多