并发症和简化:创建国际象棋应用程序
#javascript #json #ndjson

我为扁平龙学校1阶段项目所拥有的想法似乎很简单:一个单个页面应用程序,该应用程序将允许用户通过用户名搜索在线国际象棋播放器,并将显示他们的统计数据和最近的游戏。用户还可以通过页面中嵌入的iframe查看每个游戏。

当我采取第一步创建此程序时,我很快发现它比最初看起来更复杂。

我首先要查看Chess.com的API,这是使用最广泛的在线国际象棋平台。但是,我几乎立即遇到了一个大问题:游戏的iFrame的ID与游戏ID不符。据我所知,在任何API的响应中都找不到IFRAME ID,没有该ID,就无法动态地嵌入游戏的iFrame。

我搬到了第二个最受欢迎的在线国际象棋平台Lichess。幸运的是,地衣的iframe ID和游戏ID与之匹配。问题解决了,对吗?

错误。

当我看了看地立与API时,我注意到了一些不熟悉的东西。对https://lichess.org/api/games/user/{username}的提取请求返回了NDJSON(Newline Delimited JSON)格式而不是JSON格式的内容 - 我真的很熟悉的唯一格式。我将要使用的其他端点的请求 - 例如,游戏中玩的开头,所有返回的数据都以JSON格式返回的数据 - 但是我仍然需要一种访问和列出玩家最近游戏的方法。

毫不奇怪,忽略了犯罪的'Accept': "application/x-ndjson",希望使用.then(res => res.json())转换为JSON。

然后,我意识到我必须弄清楚NDJSON是什么,然后才能继续进行。根据ndjson.org的说法,NDJSON是“用于存储或流式结构化数据的方便格式,可以一次处理一个记录。” NDJSON和JSON格式之间的主要区别似乎是使用\ n将NDJSON数据分为单独的线,而JSON数据却不是。 NDJSON的每个单独行都是有效的JSON。就我而言,NDJSON的每一行都代表一个国际象棋游戏。

这是我对可以获取用户游戏列表的功能的一般想法:

function listUserGames(e) {
    let username = e.target.querySelector('#username').value

    fetch(`https://lichess.org/api/games/user/${username}?max=8`, {
        method: 'GET',
        headers: {
            'Accept': "application/x-ndjson"
        }
    })
    .then(res => 
        // somehow convert res from NSJSON to JSON
    )
    .then(games => {
        if (games.length > 0) {
            games.forEach(game => displayGame(game))
        } else {
            // indicate that the user has never played a game
        }
    })
}

Google帮助我找到了基于this repo的工作解决方案。经过一些反复试验后,我剩下以下代码:

.then(res => res.text())
.then(body => {
    let str = "[" + body.replace(/\r?\n/g, ",").replace(/,\s*$/, "") + "]"
    return JSON.parse(str)
})

这起作用了,我(主要)理解原因。它正在将响应转换为文本,添加括号以使文本像数组一样,用逗号替换新线字符,以使每条线像数组元素一样使每条线变为一行,并在将其转换为JSON之前删除空格。但是,我对此感到不舒服。我不觉得自己完全理解了语法,我不太确定为什么要删除空格。我无法复制它。我继续前进,最终完成了该应用程序,但是这个代码块仍然困扰着我,即使它效果很好。

我稍后返回此代码块,并分解了需要做的事情。

  1. 将响应转换为文本。(使用.then(res => res.text())完成)
  2. 将每条线变成数组中的元素。
  3. 转换为JSON

我知道如何将字符串分为数组,所以我使用data.split('\n')开始。

现在我有一系列的字符串。我怎么能将每个人转换为JSON?我知道每个人都是有效的JSON,如NDJSON文档所述,我意识到Map()方法对这种情况非常完美。

实施此方法后,我有以下代码:

.then(res => res.text())
.then(data => {
    return data.split('\n').map(game => JSON.parse(game))
})

我尝试了一下,...它没有用。当我运行代码时,此错误显示在我的控制台中:

Console log of error message

仔细检查后,我发现了这个问题。运行console.log(data.split('\n'))显示一个带有9个元素的数组。我只要求8场比赛。

Console log of an array of strings

我终于发现了为什么需要删除空格。

是时候再次尝试了。我使用过滤器()方法删除空字符串:

.then(res => res.text())
.then(data => {
    return data.split('\n').filter(item => item.length > 0).map(game => JSON.parse(game))
})

这仍然感觉有点不高,但是起作用了!

在撰写此博客的过程中,我意识到我可以通过在字符串末端修剪空格来使代码更简单。

这给我留下了以下代码:

.then(res => res.text())
.then(data => {
    return data.trim().split('\n').map(game => JSON.parse(game))
})

这是我想要的:一种将NDJSON转换为我完全理解的JSON的方法。这个代码块将ndjson转换为文本,修剪空格,使每行成为数组中的元素,并以json格式的每个元素返回一个数组。

Console log of chess games in JSON format

回想起来,该解决方案似乎很明显。但是,作为一个刚接触编程的人,一种新的格式似乎比实际更具吓人。我感谢现在经历的过程,因为我对执行代码时会发生的事情有更好的了解。 Chrome DevTools中的“网络”选项卡的大量调试和检查使我看到了我的数据在重新格式化过程中经历的每一个更改。在这个项目之后,我可以自信地说我了解这一过程并可以重现它。我的外卖很简单,而且很合适:事情变得复杂了,但是它们简化得很好。