何があった?
普段laravelのeloquentで出来上がったオブジェクトばかり触っているため、オブジェクト作成という初歩的なところでつまづいたので記事にしました。
せっかくなのでクイズ形式にしてみましたので、ぜひ皆さんもエラーシューティングできるか腕試ししてみてください。
問題
それでは問題ですが、下記でどこが悪いかわかりますか?
const questionnaire_contents = [
{id: 1, question: '質問1', answer_option: '{"1": "回答1", "2": "回答2", "3": "回答3"}', created_at: '2023-06-11T15:00:00.000000Z'},
{id: 2, question: '質問2', answer_option: '{"1": "回答1", "2": "回答2", "3": "回答3"}', created_at: '2023-06-11T15:00:00.000000Z'}
];
let prepare_answer_option = {};
for (const questionnaire_content of questionnaire_contents) {
for (const key in JSON.parse(questionnaire_content.answer_option)) {
prepare_answer_option[questionnaire_content.id][key] = JSON.parse(
questionnaire_content.answer_option
)[key];
}
}
エラー文の様子
Uncaught (in promise) TypeError: Cannot set properties of undefined (setting '1')
====以下で間違って答えが見えないように空白の改行を入れます====
答え
正解は"それぞれの次元で最初に空のオブジェクトを準備する必要がある"でした。
const questionnaire_contents = [
{id: 1, question: '質問1', answer_option: '{"1": "回答1", "2": "回答2", "3": "回答3"}', created_at: '2023-06-11T15:00:00.000000Z'},
{id: 2, question: '質問2', answer_option: '{"1": "回答1", "2": "回答2", "3": "回答3"}', created_at: '2023-06-11T15:00:00.000000Z'}
];
let prepare_answer_option = {};
for (const questionnaire_content of questionnaire_contents) {
prepare_answer_option[questionnaire_content.id] = {}; // この行を追加で成功
for (const key in JSON.parse(questionnaire_content.answer_option)) {
prepare_answer_option[questionnaire_content.id][key] = JSON.parse(
questionnaire_content.answer_option
)[key];
}
}
考察(ここから先はエラーで困っただけの方は読まなくて大丈夫です)
せっかくつまづいたので、なぜ今回のエラーが起きるのかコンピューターサイエンス的に考察してみました。
まず結論ですが、メモリでは変数で使う領域を2段以上飛ばして紐付けしきれないのが原因(=メモリ上で変数は一つ上の親要素との関連しか保存できないため)だと考えられます。(ポインタの仕組みより)
例として$objに格納された{A:{B:C}}という多次元オブジェクト
を作るときに、コンピューターは以下のような処理をしています。
$objの保存領域として100バイトくらい確保
↓
Aの保存領域として100バイトくらい追加確保して$objの保存先(ポインタ番号)も記憶
↓
Bの保存領域として100バイトくらい追加確保してAの保存先(ポインタ番号)も記憶しつつ、Bの内容としてCを保存
もし事前準備なしで$obj[A][B]='C'
と指定してしまうと、「objに紐づくAに紐づくBの部分の内容としてCを保存してください」という命令文になりますが、コンピュータとしては「Aの保存先は存在しませんが?せめて$objの保存先くらい準備してくださいよ」となると言えます。
なのでメモリに寄り添うと、2段飛ばしにならないようにしてあげる必要があることがわかります。
具体的には$obj={};
で$objの保存先を記憶させ、$obj[A]={}
で$objに紐づくAの保存先を記憶させてから、$obj[A][B]='C'
で$objに紐づくAに紐づくBの保存先とその内容を記憶させてあげなければならないことがわかります。
(※余談ですが、逆に$obj={A:{B:C}}
なら、$objに紐づくAに紐づくBの内容がCとなるのに必要なものを全てこの場で作って保存してくださいとなるのでエラーにならないといえることもわかります。)
そしてこの考察があっているかの確認ですが、今回のエラーを起こしたときの状況が上記の事前準備ができなかった場合のものと一致し、かつ同じ内容のエラー文を出しています。
このことからコンピューターサイエンス的に分析すると、今回のエラーはメモリでは変数で使う領域を2段以上飛ばして紐付けしきれないのが原因で間違いないと考えられます。(2段飛ばしできない理由をもっと詳しく知りたい方はポインタの原理を学んでみてください)
終わりに
ここまでつらつら考察してきて、確かに考察の内容を理解すると記憶の定着は進みそうですが、毎度考えると疲れそうだし作業が進まなそうだと思いました。
なので皆さんは今後多次元オブジェクト作成でエラーが起きた時は、考えすぎずとにかく初期値の設定を忘れていないかだけを確認していただくのが良いかなと思います。
追記
コメントいただいていた部分を、修正いたしました。
外に出てしまっており回答できずの間に消えてしまっていたのですが、とてもありがたかったです。
引き続き間違いなどありましたら、気兼ねなくご指摘いただけると嬉しいです。