0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSONを整形しただけなのに、IDが1減った

0
Posted at

JSONの整形係を作りました。担当は改行と字下げです。

ところが、数字まで丸くして返してきました。

人間関係ならありがたい配慮ですが、IDには角が立っていてもらわないと困ります。

9007199254740993 を渡したら、戻ってきたのは 9007199254740992。頼んだ字下げに、頼んでいない値下げが付いてきます。

個人で公開している After 6 ToolboxのJSON整形・圧縮 で、以前の実装にあった問題です。合成データで再現し、2026年10月22日に修正しました。

原因を追うと、整形係に余計な仕事まで任せていました。今回は、その担当業務を見直した話です。

整形係の業務マニュアル

以前の処理は、よく見るこの形です。JSON.parse() で読み、JSON.stringify() でインデント付きの文字列に戻します。

const input = '{"id":9007199254740993,"decimal":1.2300,"zero":-0,"exp":1e+400}';
console.log(JSON.stringify(JSON.parse(input), null, 2));

Node.jsで実行すると、こうなります。

{
  "id": 9007199254740992,
  "decimal": 1.23,
  "zero": 0,
  "exp": null
}

IDだけでなく、小数の末尾のゼロも片づけられ、巨大な数は null になりました。整理整頓を頼んだら、かなり踏み込んだ断捨離が始まっています。

JSON.parse() は、JSONの数値をJavaScriptの Number に変換します。安全に整数を扱える上限は 9007199254740991。それを超える整数には、正確に表現できないものがあります。

さらに JSON.stringify() は、変換後の値をJSONとして書き直します。この往復で、次のことが起きます。

元の入力 往復後 起きたこと
9007199254740993 9007199254740992 整数の精度が失われる
1.2300 1.23 値は同じでも、末尾のゼロが消える
-0 0 負のゼロの表記が消える
1e+400 null Number では Infinity となり、再出力で null になる

全部を「丸め誤差」で片づけると説明を間違えます。値が変わる問題と、表記が変わる問題が混ざっています。

整形ツールとしては、どちらも勝手に変えたくありません。しかし、実装では「JavaScriptの値に変換してから、書き直す」という手順にしていました。

担当者が指示どおりに働いた結果でした。責任者の席が近くなってきました。

書類の中身まで整理しないでください

修正後も、構文の検査には JSON.parse() を使っています。ただし、返ってきたオブジェクトを出力の材料にしません。

元の入力から文字列・数値・括弧などのトークンを取り出し、文字列の外側にある空白と改行だけを整えます。

圧縮部分を抜き出すと、考え方はこれだけです。

function compactJson(input) {
  JSON.parse(input); // 構文検査。変換結果は出力に使わない
  const tokens = input.match(/"(?:\\[\s\S]|[^"\\])*"|[{}\[\],:]|[^\s{}\[\],:]+/g) ?? [];
  return tokens.join('');
}

console.log(compactJson('{ "name": "山 田", "id": 9007199254740993 }'));
// {"name":"山 田","id":9007199254740993}

この正規表現だけでJSONの正しさを判定しているわけではありません。先に構文検査を通すのが前提です。

ここで単純にすべての空白を削ると、"山 田" が "山田" になります。数字の次は名前です。担当業務がまた広がってしまいます。

文字列をひとまとまりで取り出せば、名前の空白やエスケープもそのまま残せます。整形時は、同じトークン列の括弧やカンマに合わせて改行とインデントを足します。

この方式で、大きな整数、小数の末尾ゼロ、指数表記、負のゼロを保持するようにしました。キーの並びや重複キーも保持します。重複キーをどう扱うかといった意味上の判断も、今回の整形処理の担当から外しています。

今度は空白が働きすぎる

担当を字下げに絞っても、考えることは残ります。

配列が深く入れ子になっていると、整形で足すインデントだけでも出力が大きくなります。原稿を見やすくするために用意した余白が、原稿より幅を利かせます。頼んだ仕事には熱心です。

現行実装では、入力を20万UTF-16コード単位、整形後の出力を200万UTF-16コード単位までに制限しています。深い入れ子は再帰を使わずに処理し、出力上限を超えたら中断して、圧縮するか入力を小さくするよう案内します。

エラー時には前の結果を消し、コピーも無効にします。今回の処理は失敗しているのに、机の上には前の完成品がある。その状態でコピーできると、どの入力の結果を持ち帰るのか分からなくなるためです。

大きな整数や文字列の保持に加え、不正な構文、深い配列、出力上限を確認しました。公開画面でも、大きなIDの整形・圧縮と、不正入力時に結果が消えることを確かめています。

参考資料と補足

実装・検証・執筆には生成AIを使っています。掲載例は合成データです。再現コードはNode.js v24.22.1で確認しています。

担当業務は見直しました

今回の修正では、構文検査と、出力する文字の扱いを分けました。構文の正しさは JSON.parse() で確かめ、整形結果は元のトークンから作ります。

見た目を整えてほしい担当に、値の解釈と書き直しまで頼んでいた。最初に決めるべきだったのは、何を変えてよいかでした。

これで整形係は、数字に気を回さず、改行と字下げに専念できます。

担当業務は見直しました。責任者は続投です。

個人開発なので、交代要員がいません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?