実際にGlitch上でNode.jsのプロジェクトを作成して、
fs.writeFileSync
を試しましたが、ファイルは作られていませんね。
という訳で、そのレンタルサーバ内でファイルを作って更新することは無理です
誰か別の媒体に書き出す仕組みが必要になりますね。
一応下記、Discord.js等でローカルに持ち帰って使う事も考えられるので、
fsでファイルが書き出せる想定での回答は残しておきます。
ゴールを考えた場合その方法では無理なので少し微修正が必要になります。
JSONファイルは1ファイル内で1個の値しか所持出来ないというルールが存在します。
配列やオブジェクトを使えば1個に限らず複数個持てるやん?と思うでしょうが違います。
配列やオブジェクトという集合体を使えば、
複数個の値を包んだ1個のデータとして定義出来るよってだけで、
それを複数個書いても良いわけではありません。
NG: JSONファイル全体が1個のオブジェクトで定義されていないからダメ
json
1{"name": "taro"}
2{"name": "jiro"}
OK: これらを配列で包む事で、配列という1個の値で管理されることになりOKになった
json
1[
2 {"name": "taro"},
3 {"name": "jiro"}
4]
fsのappendFileSyncを用いてjsonファイルにオブジェクトを追加しようとしたのですが、
JSONファイルを書き出すということは
ファイルの1文字目は{
、ファイルの末尾が}
になるわけです。
これにどんな文字を追記しても不正JSONファイルになります。
(※空文字と改行コードだけなら妥当なJSONファイルであり続けますが、そんな屁理屈は言っちゃダメですよ?)
なので「追記」というアプローチでは絶対に前へ進めません。
全く同じファイル名のJSONファイルを消して作り直しというアプローチを取ることになります。
fs.writeFileSyncを使いましょう。
質問文のコードを活かしたまま書き換えるならこんな感じ
js
1if (SPLITPOINT[0].match(/ファイルに追加/i)){
2 const {id} = message.author;
3 if (DATAFILE.userid[id] == null) {
4 DATAFILE.userid[id] = [];
5 }
6 const [_, url, title] = SPLITPOINT;
7 DATAFILE.userid[id].push({url, title});
8
9 // 悔しいけどJSONに追記という手段は取れないので、全部消して保存し直す
10 fs.writeFileSync(`${__dirname}/datafile.json`, DATAFILE);
11
12 return;
13}
- nullとundefinedを判別するイディオムとして
値 == null
はよく使う
- letは最小限に留め、出来る限り使ってはならない。
letは「後で書き換えるよ」という合図になる為、コードを読む人は強烈に意識する事になる
沢山letがあると疲弊しちゃうので普段はconst使って、不都合が出た変数のみletに書き換えよう
まぁJSONを捨てちゃうというのは手ですね。
実際問題の現場ではMySQL等の高速なデータベース使うとか、
特定ディレクトリ配下に当日の日付のファイルを大量に作って都度書き出していくみたいなアプローチになると思います。
その中でも今回はJSONとほぼ変わらないYAMLを紹介しましょうか。
YAMLはJSONより前から存在するファイルフォーマットです。
これもJSONと同じく1ファイル管理型です。
ですが、YAMLはPythonみたいにインデントで親子を表現するという特徴があります。
インデントさえ揃えてしまえばファイル末尾にしれっと書き足しても妥当なYAMLファイルを維持出来るわけですね。
そもそもの記述がJSONよりイケてるので
なんでJSON使うのか分からないレベルです。
(Node.jsのrequireでJSONを呼び出すのは速度が出るので、ロード時間のみ考慮するならばJSONが有利ですが)
しかしJavaScriptはネイティブでJSONを読み書きする機能が備わっていますが、
YAMLを読み書きする機能はないので、読み書きする為のライブラリが別途必要になります。
npmのサイトで探した感じ、メンテされてて使えそうなのはこの2つですね。
私は個人的な開発でjs-yamlを使ってるのでこちらを採用するとして、
bash
1$ npm install js-yaml
YAMLで記述する場合、
ネックになるのがIDを外に出して一意にしているところですね。
あれのせいで追記しづらくなっているので追記しやすい形状に変更してしまいしょう。
(使い方は後で説明します)
js
1const yaml = require("js-yaml");
2
3if (SPLITPOINT[0].match(/ファイルに追加/i)){
4 const {id} = message.author;
5 const [_, url, title] = SPLITPOINT;
6 const writeString(yaml.dump([{id, url, title}]));
7
8 // 改行文字の\nが必要だったり不要だったりすると思うので、
9 // 実際の動きを見ながら\nを付けたり外したり調整する
10 fs.appendFileSync(`${__dirname}/datafile.yml`, "\n" + writeString);
11
12 return;
13}
確認してませんが、
多分こんな感じにデータが吐き出されるようになるはずです。
yaml
1- id: aaa
2 url: http://example.com/path1
3 title: タイトル1
4- id: bbb
5 url: http://example.com/path2
6 title: タイトル2
7- id: aaa
8 url: http://example.com/path3
9 title: タイトル3
質問文冒頭の形式でデータが欲しい場合、
LodashというJavaScriptの値の変換に特化したライブラリの
groupByを使うと楽ですね。
npm install lodash
でインストールして、
js
1const yaml = require("js-yaml");
2const _ = require("lodash");
3
4const arr = yaml.load(fs.readFileSync(`${__dirname}/datafile.yml`));
5{userid: _.groupBy(arr, ({id}) => id)}
質問文の冒頭とほぼ同じ状況が作れます。
(わざわざuseridとか付け足す意味が分からんけど)
Lodashテスターで動きを確認してみます。
js
1const arr = [
2 {id: "aaa", url: "http://example.com/path1", title: "タイトル1"},
3 {id: "bbb", url: "http://example.com/path2", title: "タイトル2"},
4 {id: "aaa", url: "http://example.com/path3", title: "タイトル3"}
5];
6result = {userid: _.groupBy(arr, ({id}) => id)};
7
8// 結果
9{
10 "userid": {
11 "aaa": [
12 {
13 "id": "aaa",
14 "url": "http://example.com/path1",
15 "title": "タイトル1"
16 },
17 {
18 "id": "aaa",
19 "url": "http://example.com/path3",
20 "title": "タイトル3"
21 }
22 ],
23 "bbb": [
24 {
25 "id": "bbb",
26 "url": "http://example.com/path2",
27 "title": "タイトル2"
28 }
29 ]
30 }
31}
Lodashを使わずネイティブJavaScriptで頑張るなら
Array.reduceを使って頑張る形になります。
もしくはfor文でしこしこ作るか。
js
1const arr = [
2 {id: "aaa", url: "http://example.com/path1", title: "タイトル1"},
3 {id: "bbb", url: "http://example.com/path2", title: "タイトル2"},
4 {id: "aaa", url: "http://example.com/path3", title: "タイトル3"}
5];
6// ついでにid値を除外しますかね
7const byId = arr.reduce((obj, {id, url, title}) => {
8 if (obj[id] == null) obj[id] = [];
9 obj[id].push({url, title});
10 return obj;
11} , {});
12result = {userid: byId};
13
14// 結果
15{
16 "userid": {
17 "aaa": [
18 {
19 "url": "http://example.com/path1",
20 "title": "タイトル1"
21 },
22 {
23 "url": "http://example.com/path3",
24 "title": "タイトル3"
25 }
26 ],
27 "bbb": [
28 {
29 "url": "http://example.com/path2",
30 "title": "タイトル2"
31 }
32 ]
33 }
34}
単純に特定ユーザーの発言のURL一覧を取り出したいだけなら、
Array.filterが使えます。
js
1const arr = [
2 {id: "aaa", url: "http://example.com/path1", title: "タイトル1"},
3 {id: "bbb", url: "http://example.com/path2", title: "タイトル2"},
4 {id: "aaa", url: "http://example.com/path3", title: "タイトル3"}
5];
6result = arr.filter(({id}) => id === "aaa");
7
8// 結果
9[
10 {
11 "id": "aaa",
12 "url": "http://example.com/path1",
13 "title": "タイトル1"
14 },
15 {
16 "id": "aaa",
17 "url": "http://example.com/path3",
18 "title": "タイトル3"
19 }
20]
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/11/25 04:04
2021/11/25 10:40