JSON语法允许对象中有重复的键吗?

290

这是有效的JSON吗?

{
    "a" : "x",
    "a" : "y"
}

http://jsonlint.com/ 表示是可以的。

http://www.json.org/ 并没有说明它是被禁止的。

但显然这并没有多大意义,对吧? 大多数实现可能使用哈希表,因此它总是被覆盖。


1
C# 的 Json.NET 如果你反序列化成 Dictionary<string, string>,会移除第一个键值对。 - Sam Leach
1
如果有人希望在JSON字符串中查找重复值的解决方案,请查看免费在线JSON验证器。链接:http://www.freeformatter.com/json-validator.html - Pepijn Olivier
1
*http://jsonlint.com/*说是有效的,但实际上它会删除除了最后一个键值对以外的所有内容,然后再进行验证,这样才能使其有效。 - Tim
14
标准被打破了。 - Bradley Thomas
2
我使用键名“--”作为注释符,而值则是单行字符串作为注释。因此,我希望没有解析器会对此抱怨。 - Lothar
显示剩余4条评论
12个回答

201
短答案:是的,但不建议这样做。
长答案:这取决于您所谓的“有效”是什么意思... ECMA-404“JSON数据交换语法”没有提到重复的名称(键)。
然而,RFC 8259“JavaScript对象表示法(JSON)数据交换格式”说:
引用:

对象内的名称应该是唯一的。

在这种情况下,SHOULD 必须按照 BCP 14 中指定的方式理解:
引用:

SHOULD 这个词或形容词“推荐”表示,在特定情况下可能存在忽略特定项的有效原因,但必须充分理解并仔细权衡选择不同路线的全部影响。

RFC 8259解释了为什么独特的名称(键)很好:

所有名称都是独特的对象在互操作性方面是可互换的,因为接收该对象的所有软件实现将就名称-值映射达成一致。当对象内的名称不唯一时,接收此类对象的软件行为是不可预测的。许多实现仅报告最后一个名称/值对。其他实现报告错误或无法解析对象,而某些实现报告所有名称/值对,包括重复项。


另外,正如Serguei在评论中指出的:ECMA-262 “ECMAScript®语言规范”指出:

如果对象内存在重复名称的字符串,则同一键的词汇上一个值将被覆盖。

换句话说,以最后一个值为准。
尝试使用由JSON创始人道格拉斯·克罗克福德创建的Java实现解析具有重复名称的字符串会导致异常:
org.json.JSONException: Duplicate key "status"  at
org.json.JSONObject.putOnce(JSONObject.java:1076)

2
JSON应该是有效的JavaScript,因此检查文字中是否有重复键是相关的。V8似乎接受它们:'d8 -e'x={"a":1,"a":2}; print(x.a);'',这将打印出2。 - user1142217
11
根据 ECMA-262 规范中JSON.parse()的明确规定,"在一个对象内有重复名称字符串时,排在前面的同名键对应的值将被后面的值覆盖。" 换句话说就是“后来者居上”。 - Serguei
6
据我所知,JSON不应该是有效的JavaScript,并且有些情况下它并不是有效的JavaScript,可以参考http://timelessrepo.com/json-isnt-a-javascript-subset。尽管如此,在JSON规范中也明确表示,它受到了JavaScript的很大启发。 - Simon Touchtech
3
值得注意的是,在使用 JavaScript 的 "严格模式" 时,如果有两个相同的键,Chrome 将使用第二个键值对并忽略第一个。而 IE11 则会抛出异常。 - Shahar

153

根据标准 (p. ii)

预计其他标准将参考此JSON文本格式的标准,严格遵守JSON文本格式,同时对各种编码细节施加限制。这些标准可能需要特定的行为。JSON本身不指定任何行为。

在标准的后面部分(p.2),提供了JSON对象的规范:

对象结构表示为一对括号标记,包围零个或多个名称/值对。名称是一个字符串。每个名称后面跟随一个冒号标记,将名称与值分开。单个逗号标记将一个值与下一个名称分隔开。

JSON对象图表

该规范没有提到重复键是无效还是有效的,因此根据规范,我可以安全地假设它们被允许。

大多数JSON库的实现并不接受重复的键,并不与该标准冲突,因为第一条引用说明了这一点。

以下是两个与C++标准库相关的示例。当将某些JSON对象反序列化为std::map时,拒绝重复键是有意义的。但是,当将某些JSON对象反序列化为std::multimap时,接受重复键就像正常一样。


6
我猜我可以接受这个答案,尽管我喜欢@PatrickGoley提到的,在json.org上它被称为一组键值对,这意味着唯一性,这意味着它是无效的。 - clamp
8
@clamp json.org不是标准,据我所知,也不是由Emca International运营。json.org似乎是匿名的。这是规范:http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf。json.org上的内容并不重要。 - Timothy Shields
5
考虑我刚刚添加的std::multimap示例。它可以被序列化为一个JSON对象,其中可能存在重复的键。 - Timothy Shields
2
@clamp 作为一组键值对,并不排除存在重复的名称。{"a":1,"a":2} 是两个不同键值对的集合。事实上,即使 {"a":1,"a":1} 可以被看作是一组只有一个元素的键值对集合。它被重复使用只是一种语法上的怪异。更好的定义应该是:“对象是从字符串(名称)到值的部分函数”。 - Marcelo Cantos
7
@TimothyShields,你提供的标准指出:“JSON语法不对用作名称的字符串施加任何限制,不要求名称字符串唯一,并且不赋予名称/值对的排序任何意义。” - Charles
显示剩余2条评论

20

有两个文件指定了JSON格式:

  1. http://json.org/
  2. https://www.rfc-editor.org/rfc/rfc7159

采纳的答案引用了第一个文件。我认为第一个文件更加清晰,但第二个文件包含更多细节。

第二个文件说:

  1. 对象

对象结构表示为一对花括号,括在零个或多个名称/值对(或成员)周围。名称是一个字符串。每个名称后面跟随一个冒号,将名称与值分开。单个逗号将值与后面的名称分隔开。 对象中的名称应该是唯一的。

因此,重复名称不是被禁止的,但也并不鼓励这样做。


14

在处理一个既能接受XML又能接受JSON的API时,我遇到了一个类似的问题,但是它没有说明如何处理你预期在JSON中出现的重复键。

以下是您的示例JSON的有效XML表示:

<object>
  <a>x</a>
  <a>y</a>
</object>

当它被转换成JSON格式时,你会得到以下内容:

{
  "object": {
    "a": [
      "x",
      "y"
    ]
  }
}

一种处理所谓的重复键的语言到另一种语言的自然映射,可以作为潜在的最佳实践参考。希望这能帮助某些人!


6
因为有很多过时的想法和混淆,所以需要发布一个帖子来解答标准相关问题。截至2017年12月,有两个竞争标准:RFC 8259 - https://www.rfc-editor.org/rfc/rfc8259 和 ECMA-404 - http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf。json.org建议使用ECMA-404作为标准,但该网站似乎不是权威机构。虽然我认为ECMA是权威机构,但重要的是,这里唯一的标准差异(关于唯一键)是RFC 8259表示键应该是唯一的,而ECMA-404表示它们不需要是唯一的。RFC-8259如下所示:

"对象中的名称应该是唯一的。"

“SHOULD”这样大写的单词在RFC世界中是有特定含义的,这个含义在另一个标准(BCP 14, RFC 2119 - https://www.rfc-editor.org/rfc/rfc2119)中有明确定义,意思是:

  1. SHOULD 这个词或者形容词“RECOMMENDED”,意思是在特定情况下可能存在忽略某个特定项的正当理由,但在选择其他方案之前必须充分理解并仔细权衡其全部影响。

ECMA-404:

“JSON语法对用作名称的字符串没有任何限制,不要求名称字符串唯一,并且不赋予名称/值对排序任何意义。”

所以,无论你怎么看待它,它在语法上都是有效的JSON

RFC 8259推荐使用唯一键的原因是:

一个对象的名称都是唯一的,这意味着所有接收该对象的软件实现都将同意名称-值映射。当对象中的名称不唯一时,接收此类对象的软件的行为是不可预测的。许多实现仅报告最后一个名称/值对。其他实现会报告错误或无法解析对象,而某些实现会报告所有名称/值对,包括重复项。
换句话说,在RFC 8259的观点下,它是有效的,但您的解析器可能会出错,并且不能保证哪个(如果有)值将与该键配对。从ECMA-404的角度来看(我个人认为是权威),它是有效的,没有任何承诺。对我来说,这意味着任何拒绝解析它的解析器都是有问题的。它应该至少根据这两个标准进行解析。但是,如何将其转换为您选择的本地对象,在任何情况下,无论是否具有唯一键,都完全取决于环境和情况,并且始终如此,没有标准。

1
json.org实际上早于ECMA标准化。我相信它实际上是由Crockford本人创建的(这就是为什么它有他的书的无耻广告)。当时它是JSON的权威。 - max

6
JSON规范如下:
一种对象是一组无序的键值对。
这里重要的部分是“无序”,因为它意味着键的唯一性,因为你可以使用唯一的键来引用特定的键值对。
此外,大多数JSON库将JSON对象反序列化为哈希映射/字典,其中键保证是唯一的。当你反序列化具有重复键的JSON对象时,取决于库:在大多数情况下,你要么会得到一个错误,要么只会考虑每个重复键的最后一个值。
例如,在Python中,json.loads('{"a": 1, "a": 2}')返回{"a": 2}。

27
无序是否意味着唯一?我认为这里关键词是“集合”。 - Patrick Goley
8
一个无序的颜色集合:蓝色,绿色,绿色,蓝色,红色,蓝色,绿色 - 其中有重复的颜色。 - Timothy Shields
5
你引用的文本短语“一个对象是一个无序的名称/值对集合”并未出现在JSON规范中。 - Timothy Shields
7
我现在明白你是在引用json.org的内容。它与官方规范“接近”,但并不是规范本身。页面顶部有一个指向规范的链接,该链接的内容与json.org完全一致。如果你搜索规范文档,你将发现单词“unordered”未出现在其中,“set”这个词只出现在与JSON对象无关的上下文中。 - Timothy Shields
5
注意它说的是“名称/值对的无序集合”,而不是名称。也就是说,{ (a,b), (a,c) } 是一个独特的集合。因此,在 json.org 的定义下,{"a":1,"a":2} 是有效的,但 {"a":1,"a":2,"a":1} 则无效。此外,请注意ECMA-404(实际标准)避免使用“set”一词:一个对象结构被表示为一对花括号标记,括号中包含零个或多个名称/值对。 - Serguei
显示剩余4条评论

5

“应该是唯一的”并不意味着“必须是唯一的”。然而,正如所述,某些解析器可能会失败,而其他解析器只会使用最后一个解析的值。但是,如果规范稍微进行了清理以允许重复,则我可以看到一种用途,即您可能有一个事件处理程序,它将JSON转换为HTML或其他格式...在这种情况下,解析JSON并创建另一个文档格式将是完全有效的...”

[
  "div":
  {
    "p": "hello",
    "p": "universe"
  },
  "div":
  {
    "h1": "Heading 1",
    "p": "another paragraph"
  }
]

然后可以轻松解析为HTML,例如:

<body>
 <div>
  <p>hello</p>
  <p>universe</p>
 </div>
 <div>
  <h1>Heading 1</h1>
  <p>another paragraph</p>
 </div>
</body>

我能理解提问的原因,但目前来看……我不会相信它。

1
你的第一个示例中的数组缺少逗号。此外,它本身也不一致。如果您要将字典用作有序集合,则外部数组也应该是对象。即 {"div":{"p":"hello","p":"universe"}, "div":{"h1":"Heading 1","p":"another paragraph"}}。现在,许多人和框架将JSON对象视为无序字典,但JavaScript和例如MongoDB的API依赖于字典中键的顺序,因此您建议的(有序字典)并不罕见。您只需要一个专门的解析器。 - binki
有序字典仍然具有唯一的键。 - Tomasz Gandor
数组可以有相关联的值吗? - Anton Krug

2

这在ECMA JSON标准中没有定义。一般来说,在标准中缺乏定义意味着“不要指望它在所有地方都能以相同的方式工作”。

如果你想冒险,"许多"JSON引擎将允许重复,并简单地使用最后指定的值。例如:

var o = {"a": 1, "b": 2, "a": 3}

变成这个:

Object {a: 3, b: 2}

但如果你不是一个赌徒,就别指望了!


2
根据由互联网工程任务组(IETF)发布的JSON当前标准RFC-7159,“对象内的名称应该是唯一的”。然而,根据定义IETF文档中使用的术语的RFC-2119,实际上“应该”意味着“...在特定情况下可能存在有效理由忽略特定项,但必须充分了解和仔细权衡选择不同方案的全部影响。”这实质上意味着虽然建议具有唯一键,但这并非必须。我们可以在JSON对象中具有重复的键,并且仍然是有效的。
从实际应用角度来看,当在JSON中发现重复的键时,将考虑最后一个键的值。

JSON没有注释 - 在最终值上面的“重复”是等效的“注释掉”。这是使用它的一个有效原因。 - Tomasz Gandor

2
要求目的不同,有不同的答案:
使用JSON序列化对象(JavaScriptObjectNotation),每个字典元素映射到一个单独的对象属性,因此为相同属性定义值的不同条目没有意义。
然而,我遇到了一个非常特定的用例中的相同问题:为API测试编写JSON示例时,我想知道如何在不破坏可用性的情况下添加注释到我们的JSON文件中。JSON规范不支持注释,所以我提出了一个非常简单的方法:
使用重复键来注释我们的JSON样本。例如:
{ "property1" : "value1", "REMARK" : "... prop1 controls ...", "property2" : "value2", "REMARK" : "... value2 raises an exception ...", }
我们正在使用的JSON序列化程序对这些“REMARK”重复项没有问题,我们的应用程序代码只是忽略这个小开销。
因此,即使在应用程序层面上没有意义,对我们来说,这些重复项为我们提供了一种有价值的解决方案,可以在不破坏JSON的可用性的情况下向我们的测试样本添加注释。

这是一个不好的想法。即使您不需要读取其中的数据,通过包含重复键,您仍然依赖于未定义的行为。一些解析器(例如Crockford的JSON-java解析器)会抛出异常并拒绝解析数据。 - Richard Smith
实际上在我们的环境中完美地工作,满足我的需求,尽管我同意你的观点,它有些超出规范 ;) - aknoepfel
@RichardSmith 我会说解析器和更新的 ES 规范已经定义了行为。 - binki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接