質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Q&A

1回答

3559閲覧

Node.jsでGlitch上のjsonファイルにオブジェクトを書き込む方法(DiscordBot)

KABUNEGI

総合スコア0

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

1グッド

1クリップ

投稿2021/11/24 13:44

編集2021/11/24 15:21

前提・実現したいこと

プログラミング歴数か月のド初心者です。Node.jsでGlitch上にディスコードのbotを作っています。
ユーザーが送信したメッセージをJSONオブジェクトに変換し、JSONファイルに書き込むことで保存したいと考えています。

具体的には、ユーザーが「(url),(urlのタイトル)」という形式でメッセージを送信した場合に、

・メッセージを送信したユーザーのIDを取得する
・メッセージを {"url": "(url)", "title": "(urlのタイトル)"} というjsonオブジェクトに変換する
・data.jsonというファイル内にユーザーIDと一致するオブジェクトが無ければ新たに作成する
・data.jsonのユーザーIDと一致するオブジェクトに、上で変換したjsonオブジェクトを配列として追加する

というコードを組みたいと考えています。

該当のソースコード

Nodejs

1const discord = require('discord.js'); 2const fs = require('fs'); 3const DATAFILE = require('./datafile.json'); 4//jsonファイル自体はこれで読み込めている模様です 5const SPLITPOINT = message.content.split(','); 6//ユーザーの送信したメッセージを[,]位置で区切って配列化 7 8if (SPLITPOINT[0].match(/ファイルに追加/i)){ 9 if (DATAFILE.userid[message.author.id] == undefined){ 10 //data.jsonのuseridにmessage.author.idを名前とする新たな配列を作成する関数 11 } 12 let addURL = SPLITPOINT[1]; 13 let addTITLE = SPLITPOINT[2]; 14 let jsonobj = JSON.stringify({url: addURL, title: addTITLE}); 15 console.log(`addobj ${jsonobj}`); 16 //DATAFILE.userid[message.author.id]にjsonobjを追加する関数 17 return; 18} 19 20/*行いたい処理(jsonファイルの中身については下に記載) 21ⅰ.ユーザー(ID:123456789)がメッセージ「ファイルに追加,TESTURL,TESTTITLE」を送信した場合 22下記jsonファイルの"123456789"の配列の末尾に{"url": "TESTURL", "title": "TESTTITLE"}を追加 23 24ⅱ.ユーザー(ID:123456789以外)がメッセージ「ファイルに追加,TESTURL,TESTTITLE」を送信した場合 25下記jsonファイルの"userid"に配列"(message.author.id)"を追加 26上の配列の末尾に{"url": "TESTURL", "title": "TESTTITLE"}を追加*/ 27

以下datafile.json

JSON

1{ 2 "userid": { 3 "123456789": [ 4 { 5 "url": "", 6 "title": "" 7 }, 8 { 9 "url": "", 10 "title": "" 11 } 12 ] 13 } 14} 15

試したこと

fsのappendFileSyncを用いてjsonファイルにオブジェクトを追加しようとしたのですが、jsonファイルへの書き込みができません。また、fsを用いる場合、書き込み先となるファイルの特定のオブジェクトや配列に追加する方法がよくわかりません。

補足情報(FW/ツールのバージョンなど)

Node.js 12.x
discord.js 11.6.4

ito-noizi❤️を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

実際に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 03:43

編集2021/11/25 04:34
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi-sun

2021/11/25 04:04

そもそもGlitch上でファイルの読み書きが本当に出来るのかに関して全く知らずに回答してしまった…… 調査しますかね。
KABUNEGI

2021/11/25 10:40

該当の処理だけでなくコードを書く上での通則まで親切に教えてくださりありがとうございます。 色々試していたところ、今のところ再現性はないのですが、Glitch上のフォルダにちゃんとjsonが出力されるケースが確認できました。(コンソールログを見ると、何らかの要因でNode.jsが再インストールされたタイミングでjsonが出力されているように見えましたが、正確な条件は確認中です) 当面はjsonで挙動を確認しつつ、安定してwritefileが動作するようになり次第、教えて頂いた方法を試そうと思います。 素人質問で申し訳ないのですが、jsonファイルの内容を編集したい場合、 ①元のjsonファイルの配列を読み込む ②読み取った配列を操作し要素を追加・削除する ③新たなjsonファイルとしてwritefileする という処理を行うことで疑似的にファイルのデータを更新する、という考え方でよろしいでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問