为什么和何时需要对JSON对象进行扁平化处理?

22

我很惊讶在StackOverflow上没有人问过这个问题。

查看JSON对象文档和快速谷歌搜索都没有得到令人满意的结果。

它有什么优势?它是如何工作的?


编辑:为了更清晰,看一下这个展开/折叠的例子。

最快展开/折叠嵌套的JSON对象

谢谢。


1
你的意思是压缩吗?为了减小文件大小,节省磁盘空间或带宽(通常是带宽)。 - mason
你指的是哪个 JSON 库? - evanwong
1
没有必要像你链接中描述的那样“展平”JSON。(事实上,这有点违背了JSON的“哲学”。)有时JSON构造不良,有多余的“对象”层是不必要的,但所提到的示例并非如此。(虽然我想,在某些Javascript场景中,按照那里描述的“展平”可能是有用的,更多地与Javascript API有关,而不是JSON本身。) - Hot Licks
你想要解决什么问题? - SergeyB
@ike_love 只是出于好奇,试着弄清它的目的。 :) - wilbeibi
显示剩余3条评论
3个回答

9

有很多情况下,你会得到一些由某个库自动构建的JSON文本。在编程语言中,有很多库可以构建JSON文本(一个例子在这里)。

每当库添加一些额外的对象或数组包装时,您可能希望摆脱它们,因为您将JSON发送到服务器并且您在那里的代码崩溃,因为它期望原始值而不是对象(或数组)。或者,如果您的JSON是服务器响应,则不希望生成的Javascript代码必须区分对象/数组或非对象/数组。在所有这些情况下,展平都是有用的,因为它将节省您的时间。您将必须实现较少的if / else,并且可以可靠地期望数据结构尽可能平坦。

改进上述场景的另一种方法是以最大健壮的方式编写代码,以便它永远不会因多余的包装而崩溃。因此,始终预期一些包装并获取其内容。然后,展平就不需要了。

你看,这取决于构建JSON的内容以及解析它的内容。构建可能超出您的范围。

这也引出了数据模型问题。如果某些 XY 没有 0 条记录或有 >0 条记录,则我曾经使用需要不同解析方式的 XML 代码进行工作。拥有一个允许具有 0 或多个 XY 条目的包装器将使生活更加轻松。这些是数据模型决策。
在所有情况下,如果 JSON 表示我手动组合的对象结构,我希望它 不会改变。因此,对我详细设计的内容进行展平将会干扰。就我所见,标准操作并不需要展平(例如 JSON.stringify()json_encode() 等)。

8
这是一个简单的场景:在一个Web应用程序中,您有一个HTTP POST正在更新一个复杂的关系对象。
POST
update=1
&user.id=12345
&user.email=testmail@domain.tld
&user.profile.name=Mr. Test
&user.profile.age=42
&user.profile.friend.0.email=tom@domain.tld
&user.profile.friend.1.email=sally@domain.tld
&user.profile.friend.2.email=bob@domain.tld
&user.profile.skill.0.id=100
&user.profile.skill.0.name=javascript
&user.profile.skill.1.id=200
&user.profile.skill.1.name=piano

所有内容都已经在一个平面结构中,为什么不采用简单的一对一绑定呢?如果您有一系列需要执行的约束或安全要求,您可以通过直接在排序键列表上搜索来验证它们。

平面结构更易于人们理解和处理,甚至与数据库去规范化存在一些交叉。它还允许以可读性更强但更冗长的方式实现特定上下文的安全性和约束。

当完整显示用户视图时,您可能希望隐藏用户技能列表中主键ID的显示。

"user.profile.skill.#.id": { hidden: true, readonly: true }

但是,当直接查看技能(作为管理员可能需要编辑技能)时,您可能想要查看ID。

"skill.id": { readonly: true }

如果你正在编写一个以用户为中心/自助式的CMS应用程序,你可以使用简单的平面模型(基于嵌套关系模型的平面抽象)来吸引更多用户参与和贡献,而不仅仅是使用嵌套模型。简单的平面模型比嵌套模型更易于阅读。尽管程序员可以处理嵌套模式、递归解析和处理,但最终用户和管理员通常更喜欢它们被抽象化掉的部分。
TLDR: 平面比嵌套更容易阅读。虽然程序员可以处理嵌套模式,递归解析和处理;但最终用户和管理员通常更喜欢将其抽象化处理掉。

8
我知道这是一个5年前的问题,但我认为,如果有人碰到类似的用例并发现这很有用,我会补充我的想法。
将JSON对象展平的一个使用场景是通过正则表达式(RegEx)字符串插值实现动态模板绑定。这听起来很啰嗦,其实就是“填充字符串模板而不硬编码”的简单解释。
假设你有一个电子邮件模板字符串,像这样:
Hello {{firstName}},

It is amazing you chose to join our site. We are happy to have you on board. 
To get started, we would really love it if you can confirm your email address
by clicking on the link: {{confirm_url}}.

Welcome aboard

The Team!

假设我们有以下 JSON 对象在内存中:

{
   "user" : {
               "prefix"      : "Dr.",
               "firstName"   : "Awah",
               "lastName"    : "Teh",
               "email"       : "awah@superduperubercoolsite.com",
               "address"     : {
                                  "street": "100 Main St",
                                  "city"  : "PleasantVille",
                                  "state" : "NY",
                                  "phone" : "+1-212-555-1212"
                               }
            },
   "meta" : {
               "confirm_url" : "http://superduperubercoolsite.com/confirm/ABC123"
            }
}

使用正则表达式替换似乎非常简单,如下所示(假设我们的电子邮件模板字符串存储在名为template的变量中,json对象存储在名为templateData的变量中):

template = template.replace(new RegExp('{{firstName}}', 'g'), templateData.user.firstName);
template = template.replace(new RegExp('{{confirm_url}}', 'g'), templateData.meta.confirm_url);

简单易懂,对吧? --> 实际上是的!但如果这个邮件有10个模板字段呢?或者你想将模板与代码解耦,将其存储在像SendGrid这样的单独系统中,让你酷炫的市场负责人可以访问模板并更改复制语言,而无需打电话给工程师进行代码更改、测试和重新部署(多么麻烦)。

这正是展开JSON的拯救之处!

现在有许多展开JSON的方法,我在这个codepen链接中编写了一个逻辑来展开JSON(实际上,我在方法flattenJSONIntoKVPflattenJSONIntoRAW中演示了两种相似但不同的方法,请查看它们!)。

话虽如此,还有其他的实现方式,值得记住的是,本篇文章的重点是讨论为什么JSON展开可能有用,而不是如何展开。

接下来!假设您使用我的实现方式将上面的JSON(结果为键值对)展开成以下内容:

[
   { "key": "user.prefix",         "value": "Dr."},
   { "key": "user.firstName",      "value": "Awah"},
   { "key": "user.lastName",       "value": "Teh"},
   { "key": "user.email",          "value": "awah@superduperubercoolsite.com"},
   { "key": "user.address.street", "value": "100 Main St"},
   { "key": "user.address.city",   "value": "{PleasantVille"},
   { "key": "user.address.state",  "value": "NY"},
   { "key": "user.address.phone",  "value": "+1-212-555-1212"},
   { "key": "meta.confirm_url",    "value": "http://superduperubercoolsite.com/confirm/ABC123"},
]

现在,朋友,你已经开始使用GAS了!

为什么呢?因为现在你可以动态地使用来自JSON对象的值对模板字符串进行插值而不必过度关注JSON的结构(如果由于应用程序演变而导致JSON发生更改,您不必也记得来到这里更改此插值代码-您只需要更新电子邮件模板本身,这是在SendGrid上[根据此示例])。

那么要如何做呢?简单,迭代。让我们假设从上面扁平化的内容存储在一个名为flatJSON的变量中:


///Notice how I use Javascripts native string interpolation to create my RegExp

///Also note that I am replacing the dot (.) in my flattened JSON variable names with a double underscore (__), I only do this because my intended target is SendGrid, and I don't believe it likes dots in its template placeholders.
flatJSON.forEach(kvp=>template = template.replace(new RegExp(`{{${kvp.key.replace(/\./g, '__'}}}`, 'g'), kvp.value));

仅仅一行代码就可以替代可能需要十甚至数百甚至上千行代码(好吧..也许不是上千行,但你明白我的意思)。

哦!几乎忘了,我们需要更新我们的模板字符串。

请注意,现在,在我们的新模板字符串中,我们可以使用类似完全限定域名的样式变量来映射回我们的原始JSON(理想情况下,如果SendGrid支持在其模板占位符中使用点号,这看起来会非常棒,但有时候不能赢得所有!)。

Hello {{user__firstName}},

It is amazing you chose to join our site. We are happy to have you on board. 
To get started, we would really love it if you can confirm your email address
by clicking on the link: {{meta__confirm_url}}.

Welcome aboard {{user__prefix}} {{user__lastName}}!

The Team!

大功告成!

就这样,我们今天完成了一些好事:

  1. 回答了为什么要展平JSON对象的问题。
  2. 我们通过codepen示例探讨了如何展平。
  3. 我们还概述了一个使用案例,利用JSON展平可以帮助您编写耐用的动态代码,随着底层对象结构的变化而发展,而无需使用大而丑陋的eval方法(我们可以在另一篇文章中讨论大而丑陋的eval)。

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