JSON.parse()で日付をパースするときの懸念点

#64
2022.10.4

JSONには日付を表現するための書き方が存在しない。多くの場合にはISO 8601で表現したものを文字列化して格納することが多いと思う。例えばこんな具合に。

{
  "date": "2022-10-04T03:41:07.950Z"
}

しかしこれを単純にJSON.parse()すると、得られるのは当然Stringになる。これをDateとしてパースしたいがために、JSON.parse()にreviverを与えて、こういうようなコードを書いてしまうことがある。

const timestampExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;

function parseJSON(json) {
  return JSON.parse(json, (key, value) => {
    if (typeof value == 'string' && timestampExp.test(value)) {
      return new Date(value);
    } else {
      return value;
    }
  });
}

こうすれば確かに、2022-10-04T03:41:07.950ZのようなISO 8601の文字列をDateオブジェクトに変換することができる。

ただ、このコードは問題がある。次のようなJSONをパースすることを考えてみよう。

{
  "title": "記事のタイトル",
  "postedAt": "2022-10-04T03:41:07.950Z",
  "content": "..."
}

ここで、titleプロパティはユーザーが自由に入力できる文字列であるとする。もし仮にユーザーが、2022-10-04T03:41:07.950Zのような文字列をタイトルとして設定したらどうなるか。さっきのparseJSONでこのJSONをパースすると、titleがDateとしてパースされてしまうのだ。titleがStringであることを前提としたコードがあったら、これは確実にエラーを引き起こす。

この問題にどう対処すればよいか。ユーザーによる入力の段階で、日付と認識されるものを弾く、という方法も無くはない。だが根本的な問題はJSON.parseのreviverで、文字列の形式だけを見てパースする方法を変えていることだ。Dateが入るプロパティのkeyを判断した上でDateに変換するべきだろう。そうしないと、意図しないプロパティにDateが入ってしまう。

JSONをパースする段階では日付に対して何も処理を加えない、というのがよい方法かもしれない。そもそも、JavaScriptのDateはmutableであり、意図しない変更が発生する可能性が常に伴う。だから、日付は常にstringで保持し、必要に応じてDateに変換、というのもありだろう。