前提
以下のアルゴリズム問題を解いていた時に発生したエラーです。
問題概要
整数 n が与えられたとき、以下の条件に基づいて出力を生成するラムダ関数 fizzbuzz を作成する
- n が 3 の倍数であれば "Fizz" を生成
- n が 5 の倍数であれば "Buzz" を生成
- n が 15 の倍数であれば "FizzBuzz" を生成
- 上記のどの条件にも当てはまらない場合は、数値 n 自体を生成
作成した関数を用いて、提供されたテストケースで関数の出力を表示すること
なお、出力は 1 から n までの各数値に対して上記のルールに基づき生成し、各値はハイフン - で区切って連結
テストケース
ケース | 入力 | 出力 |
---|---|---|
1 | 9 | 1-2-Fizz-4-Buzz-Fizz-7-8-Fizz |
2 | 20 | 1-2-Fizz-4-Buzz-Fizz-7-8-Fizz-Buzz-11-Fizz-13-14-FizzBuzz-16-17-Fizz-19-Buzz |
使用言語: JavaScript
エラー内容
undefined:1
1-2-Fizz-4-Buzz-Fizz-7-8-Fizz
^
SyntaxError: Unexpected non-whitespace character after JSON at position 1
at JSON.parse (<anonymous>)
at Object.<anonymous> (/Users/mavo/project/algorithm-solutions/HigherOrderFunc/problems/01_fizzbuzz/js/tests/fizzBuzzTest.js:16:30)
at Module._compile (node:internal/modules/cjs/loader:1369:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
at Module.load (node:internal/modules/cjs/loader:1206:32)
at Module._load (node:internal/modules/cjs/loader:1022:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at node:internal/main/run_main_module:28:49
Node.js v20.12.2
実装コード
let fizzbuzz = n => {
let resStr = "";
for(let i = 1; i <= n; i++){
if(i % 15 === 0) resStr += "-FizzBuzz";
else if(i % 5 === 0) resStr += "-Buzz";
else if(i % 3 === 0) resStr += "-Fizz";
else resStr += `-${i}`;
}
return resStr.slice(1);
}
module.exports = fizzbuzz;
const fizzbuzz = require('../src/fizzbuzz.js')
// テストケース
const tests = {
"case1" : {
"input" : 9,
"output" : "1-2-Fizz-4-Buzz-Fizz-7-8-Fizz"
},
"case2" : {
"input" : 20,
"output" : "1-2-Fizz-4-Buzz-Fizz-7-8-Fizz-Buzz-11-Fizz-13-14-FizzBuzz-16-17-Fizz-19-Buzz"
}
};
// 検証用ループ
for(let [key, value] of Object.entries(tests)){
const res = fizzbuzz(value['input']);
const finalResult = JSON.parse(res) === JSON.parse(value['output']) ? "True" : "False";
console.log(`Test ${key}: ${finalResult}`);
}
エラー箇所の特定
...
at JSON.parse (<anonymous>)
...
スタックトレースの最後にある、JSON.parse (<anonymous>)
の部分がエラーの発生源とわかります。
エラーの意味
SyntaxError: Unexpected non-whitespace character after JSON at position 1
構文エラー: JSONとして有効な部分を読み取った後、2文字目(インデックス1)に予期しない非空白文字が見つかった
不正な JSON
JSON.parse が JSON の文法に適合しない文字列を受け取った場合、 SyntaxError が発生します。
※https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse より引用
原因
通常のケース
→ JSONの構文ルールに違反している可能性
今回のケース
→ 文字列をJSONとして解釈させようとしたこと(JSON.parse()
を使用していること)
そもそも、この課題のゴールは文字列を出力することです。
そのために、期待される出力結果(文字列)と実際の処理結果(文字列)を比較して、正しいかどうかをテストしています。
このテストにおいては、期待される出力結果も実際の出力結果も文字列であるため、JSON.parse()
を使う必要がなかったということです。
JSON.parse()
の処理
JSON.parse()の内部動作(簡易版)
JSON.parse()に文字列が渡されると、パーサは文字列の先頭から1文字ずつ読み込みます。
- 最初に読み込む文字が、JSONの有効な開始文字({, [, ", 0-9, t, f, n)であるかをチェックします
- もし有効な文字であれば、パーサはそのデータ型(オブジェクト、配列、文字列、数値など)の解析を開始します
- 有効な文字でなければ、即座にSyntaxErrorを投げます
※ Gemini の解説より引用
逐次、文字を読み取り、ルールに反していないかチェックしているようです。
今回のケースでは、「1」の後に「-」(ハイフン)があり、ハイフンは空白文字ではないため、JSONの構文ルールに違反していると判断し、エラーを投げたと判断できます。
つまり、有効なJSON要素(例: 数値1)の後に続く文字は、構文上許容される文字(数値の残りの部分、カンマ、閉じ括弧、あるいは空白)である必要があります。ハイフンはそれらのどれでもないため、予期せぬ文字として扱われたことを意味します。
有効なJSONの形式
主にオブジェクトと配列の形式があります。
1. オブジェクト
オブジェクトは、キーと値のペアの集まりです。
キーは必ずダブルクォーテーション「""
」で囲まれた文字列で、コロン「:
」の後に値が続きます。
複数のペアはカンマ「,
」で区切られます。
{
"name": "mavo",
"age": 25,
"isStudent": false,
"hobbies": ["reading", "walking"],
"address": {
"city": "Tokyo",
"zipCode": "100-0001"
}
}
2. 配列
配列の要素には、文字列、数値、オブジェクトなど、あらゆるJSONのデータ型を入れることができます。
[
"apple",
"banana",
{
"name": "orange",
"color": "orange"
},
123,
null
]
JSONで値として使えるデータ型
- 文字列: ダブルクォーテーション " で囲む(例: "Hello")
- 数値: 整数または小数(例: 123)
- ブール値: true または false
- null
- オブジェクト:
{}
で囲む - 配列:
[]
で囲む
補足: JSON.stringify()
JSON.stringify()
は、JavaScript の値(オブジェクトや配列など)を JSON 形式の文字列に変換する関数です。
例えば、FizzBuzz の結果を配列で保持している場合、そのままでは配列同士の比較が難しいですが、JSON.stringify()
を使えば文字列として比較できます
const arr1 = [1, 2, "Fizz"];
const arr2 = [1, 2, "Fizz"];
console.log(JSON.stringify(arr1) === JSON.stringify(arr2));
// 出力: true
オブジェクトや配列をそのまま比較したいときには JSON.stringify()
が有効です。
一方、今回の課題のように「最終出力が単なる文字列」である場合は、===
による比較で目的を達成できます。
まとめ
この記事で分かったこと
- 目的に応じた手段を採用できているかチェックすること
- JSONには有効な形式があること
-
JSON.parse()
の内部処理 - 構文チェックを逐次行なっていること
JSON.parse()
は文字列に変換できるものだと勘違いしていたことが、今回のエラーを招いたことがわかりました。
最後までお読みいただき、ありがとうございました。
参考URL